Middleware in Laravel: A Deep Dive
Middleware in Laravel: Tips & Tricks
Middleware in Laravel: Tips & Tricks
Tip: Middleware Order Matters
Middleware runs in the order you register it. Put authentication before authorization:
$middleware->append([
CheckAuth::class, // runs first
CheckPermission::class, // runs second
]);
Gotcha: Global Middleware Runs on EVERY Request
Even on static assets if they go through Laravel. Be careful with heavy middleware in the global stack.
Tip: Use Middleware Groups for API Versioning
$middleware->appendToGroup('api:v1', [
ApiV1Compatibility::class,
]);
$middleware->appendToGroup('api:v2', [
ApiV2Compatibility::class,
]);
Tip: Terminable Middleware for Logging
public function terminate(Request $request, Response $response): void
{
Log::info('Request completed', [
'method' => $request->method(),
'status' => $response->getStatusCode(),
'duration' => microtime(true) - LARAVEL_START,
]);
}
Gotcha: Middleware Singletons
If you want the same middleware instance for handle() and terminate(), register it as a singleton:
$this->app->singleton(TerminatingMiddleware::class);
Otherwise Laravel creates a fresh instance for terminate().
Tip: Skip Middleware for Specific Routes
Route::middleware([AuthMiddleware::class])->group(function () {
Route::get('/dashboard', ...);
Route::get('/public', ...)->withoutMiddleware([AuthMiddleware::class]);
});
Tip: Middleware Parameters for Role Checking
public function handle(Request $request, Closure $next, string ...$roles): Response
{
if (! $request->user()->hasAnyRole($roles)) {
abort(403);
}
return $next($request);
}
// Usage:
Route::get('/admin', ...)->middleware('role:admin,superadmin');
Tip: Use route:cache Carefully
php artisan route:cache is fast, but it doesn't work with closure-based routes. Every time you cache routes, Laravel serializes them. If you have Route::redirect() or closure callbacks, the cache breaks. Stick to controller-based routes in production.
Tip: Model APP_KEY Rotation
Rotating APP_KEY invalidates all encrypted data — cookies, encrypted DB columns, and password reset tokens. If you must rotate (e.g., after a leak), plan a migration that re-encrypts existing data with the new key.
Gotcha: Local Scope Leaks
Global scopes defined in booted() apply to ALL queries on that model — including relationships. An innocent User::all() in admin panel might exclude soft-deleted users if a global scope is active.
Senior Insight
Middleware ordering is consistently one of the top five sources of subtle bugs I've encountered in Laravel codebases. I've seen entire features break because rate-limiting ran after the controller logic, or because CORS headers were applied in the wrong position. My rule after years of debugging this: request-modifying middleware (CORS, session, TrimStrings) goes first, then authentication, then authorization, and finally rate-limiting. Draw a pipeline diagram before writing a single middleware class.
Source: Laravel News (https://laravel-news.com/), Freek.dev (https://freek.dev/tags/laravel), Spatie Blog (https://spatie.be/blog)