andydefer/laravel-nemesis
Laravel Nemesis is a lightweight Laravel package by andydefer that installs via Composer and registers through a service provider, adding configurable functionality to your app. Early-stage project with limited adoption—check the source and docs before production use.
composer require <vendor>/nemesis # confirm vendor from Packagist
php artisan vendor:publish --tag=nemesis-config
php artisan migrate
php artisan nemesis:create --domain=partner.example.com --name="Partner API"
Capture the plaintext token once — it is typically not recoverable afterwards.nemesis middleware alias:
Route::middleware('nemesis')->prefix('api/v1')->group(function () {
Route::get('/orders', [OrderController::class, 'index']);
});
config/nemesis.php — token extraction sources, quota defaults, CORS behaviour, header names.NemesisServiceProvider — confirms the middleware alias and whether the alias can be overridden.php artisan nemesis:list — fastest way to verify the package is wired up and see live quota state.First use case: drop the middleware on an existing internal API, create one token per consuming domain, and watch nemesis:list for a week before enabling blocking behaviour in production. That gives you real traffic shape before quotas start returning errors.
One token per domain, not per user. The package's unit of control is the domain. Model your issuance around consumers: web-app, mobile-ios, partner-acme. This makes nemesis:block a clean kill switch for a single integration without touching the others.
Route group layering. Place nemesis on the API group, then layer authorisation on top:
Route::middleware(['nemesis', 'auth:sanctum'])->group(...);
Nemesis answers "is this caller allowed to call at all and within quota"; auth answers "who are they". Keeping those separate avoids conflating rate-limits with user identity.
Quota lifecycle. The package blocks automatically when the counter is exhausted. Plan the reset explicitly:
// routes/console.php (Laravel 11+) or app/Console/Kernel.php
Schedule::command('nemesis:reset')->dailyAt('00:00');
Pick a reset cadence that matches your quota window semantics, and confirm whether nemesis:reset clears all tokens or accepts a --domain/--token filter.
Decommissioning vs. blocking. Prefer nemesis:block over deleting a token when a client misbehaves — it preserves the row for audit and is reversible with nemesis:unblock. Reserve deletion for genuinely retired integrations.
Issuance in code. Rather than shelling out to Artisan from a controller, wrap issuance in a small service that calls the package's model/repository directly. Your admin UI then has one seam to swap if the package's API shifts, and you can attach your own metadata alongside.
Query-param extraction as a compatibility path. Use the ?token=-style fallback only for callers that cannot set headers (legacy webhooks, some embeddable clients). For everything else enforce Authorization: Bearer.
Testing. Create tokens in setUp() (or a factory if one ships), then assert the three distinct failure shapes your clients will see: missing/invalid token, blocked token, quota exceeded. Catching these in tests is the cheapest way to pin down the exact status codes you are committing to.
Referer headers, and browser history. If you enable that extraction path, scrub the parameter in your logging middleware and never enable it on routes reachable from a browser.HandleCors and config/cors.php (or fruitcake/laravel-cors on older apps) also emit headers. Two sources means duplicated or contradictory Access-Control-Allow-Origin headers, which browsers reject outright. Pick one owner per route group and verify with curl -I -H "Origin: ...".nemesis runs before or after HandleCors depending on registration; a 401/403 from Nemesis that lacks CORS headers shows up in the browser as an opaque CORS error, not the real failure. Order Nemesis after CORS so error responses stay readable to browser clients.nemesis alias (or one you add later) will conflict silently. Check bootstrap/app.php / Kernel::$middlewareAliases after install.increment() / UPDATE ... SET used = used + 1) rather than read-modify-write; the latter overshoots quotas under concurrency. Worth a quick parallel-request test before you trust the blocking.example.com matches www.example.com, non-default ports, http vs https, and Origin vs Host header are all decisions the package makes for you. Test each case your real clients use — subdomain mismatches are the most common false 403.nemesis:list shows tokens pinned at 100%.config/nemesis.php in a deployed environment, run php artisan config:cache — long-lived config caches are a classic "my change did nothing" source.^1.0 and read the changelog on minor bumps for new config defaults.Extension points worth checking on day one: custom middleware subclassing for bespoke error responses, model swapping (if the package binds an interface), and any events fired on block/quota-exceeded — those events are the natural hook for alerting (Slack/PagerDuty) and for audit logging, and they're cheaper than polling nemesis:list.
How can I help you explore Laravel packages today?