Laravel Middleware: Your App's Bouncer 🚪
Laravel Middleware: Your App's Bouncer 🚪
Ever walked into a club and had a bouncer check your ID, pat you down, and make sure you're on the list? That's EXACTLY what middleware does for your Laravel app!
Middleware sits between the request and your controller, deciding who gets in, who gets kicked out, and who needs to wait in line. Let's dive into the good stuff!
What Even IS Middleware? 🤔
Think of it like a security checkpoint at the airport:
- Request comes in → "Show me your passport!"
- Middleware checks it → "Hmm, looks good!"
- Request continues to controller → "Welcome aboard!"
Or if things go wrong:
- Request comes in → "Show me your passport!"
- Middleware checks it → "NOPE! You're not authorized!"
- Request gets rejected → "Security! We got a live one!"
The beauty: You write this logic ONCE and apply it to multiple routes. DRY at its finest! 🌵
1. Making Your Own Middleware (It's Easier Than You Think) 🛠️
Let's say you want to log every request to your API for debugging:
php artisan make:middleware LogApiRequests
Laravel creates a file for you in app/Http/Middleware/LogApiRequests.php:
class LogApiRequests
{
public function handle(Request $request, Closure $next)
{
// BEFORE the request hits your controller
Log::info('API Request', [
'url' => $request->fullUrl(),
'method' => $request->method(),
'ip' => $request->ip(),
'user_id' => auth()->id()
]);
$response = $next($request); // Pass it along!
// AFTER the controller does its thing
Log::info('API Response', [
'status' => $response->status()
]);
return $response;
}
}
Translation: You get to run code before AND after your controller. It's like having superpowers! 🦸♂️
2. Check User Roles (The Right Way) 👮
Bad way: Checking roles in EVERY controller method:
public function deleteUser(User $user)
{
if (!auth()->user()->isAdmin()) {
abort(403, 'Not allowed!');
}
$user->delete();
}
public function editUser(User $user)
{
if (!auth()->user()->isAdmin()) {
abort(403, 'Not allowed!');
}
// more code...
}
Copy-paste nightmare! 😱
Good way: Make middleware handle it!
php artisan make:middleware EnsureUserIsAdmin
class EnsureUserIsAdmin
{
public function handle(Request $request, Closure $next)
{
if (!auth()->check() || !auth()->user()->isAdmin()) {
abort(403, 'You shall not pass! 🧙♂️');
}
return $next($request);
}
}
Register it in app/Http/Kernel.php:
protected $middlewareAliases = [
// ... other middleware
'admin' => \App\Http\Middleware\EnsureUserIsAdmin::class,
];
Now use it like a boss:
// Single route
Route::delete('/users/{user}', [UserController::class, 'destroy'])
->middleware('admin');
// Multiple routes
Route::middleware(['auth', 'admin'])->group(function () {
Route::get('/admin/dashboard', [AdminController::class, 'index']);
Route::delete('/users/{user}', [UserController::class, 'destroy']);
Route::post('/settings', [SettingsController::class, 'update']);
});
Pro Tip: You can stack middleware like pancakes! Each one runs in order. 🥞
3. Rate Limiting (Stop the Script Kiddies) 🛡️
Someone's hammering your API with 1000 requests per second? Time to slow them down!
Laravel has built-in rate limiting, but let's make it better:
// In routes/api.php
Route::middleware('throttle:60,1')->group(function () {
Route::get('/posts', [PostController::class, 'index']);
});
Translation: 60 requests per 1 minute. After that? "Slow down, cowboy! 🤠"
Want to get fancy? Different limits for different users:
class ApiRateLimit
{
public function handle(Request $request, Closure $next)
{
$user = auth()->user();
// Premium users get more requests
$limit = $user?->isPremium() ? 1000 : 60;
$key = 'rate_limit:' . ($user?->id ?? $request->ip());
if (RateLimiter::tooManyAttempts($key, $limit)) {
return response()->json([
'error' => 'Too many requests. Upgrade to premium? 😏'
], 429);
}
RateLimiter::hit($key, 60); // 60 seconds decay
return $next($request);
}
}
Now free users get 60 requests, premium users get 1000. Capitalism! 💰
4. Transform Requests On-The-Fly 🎭
Scenario: Your API receives data in snake_case but your Laravel app uses camelCase.
Instead of transforming in every controller:
class ConvertRequestToCamelCase
{
public function handle(Request $request, Closure $next)
{
$input = $request->all();
$camelCased = [];
foreach ($input as $key => $value) {
$camelCased[Str::camel($key)] = $value;
}
$request->replace($camelCased);
return $next($request);
}
}
Magic! user_name becomes userName automatically. Your controllers stay clean!
5. Add Custom Headers (For APIs) 📋
Want to add security headers or version info to every response?
class AddApiHeaders
{
public function handle(Request $request, Closure $next)
{
$response = $next($request);
$response->headers->set('X-API-Version', '2.0');
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-Frame-Options', 'DENY');
$response->headers->set('X-XSS-Protection', '1; mode=block');
return $response;
}
}
Security tip: Those X-headers protect against common attacks. Your frontend devs will thank you!
6. Maintenance Mode (The Classy Way) 🚧
Bad way: Taking your entire site down with php artisan down.
Better way: Only block certain routes!
class MaintenanceMode
{
public function handle(Request $request, Closure $next)
{
$maintenance = Cache::get('admin_panel_maintenance', false);
if ($maintenance && !auth()->user()?->isAdmin()) {
return response()->json([
'message' => 'Admin panel is under maintenance. Back soon! ⚙️'
], 503);
}
return $next($request);
}
}
Apply to admin routes:
Route::middleware(['auth', 'maintenance'])->prefix('admin')->group(function () {
// Admin routes here
});
Now you can maintain the admin panel while keeping the public site running! 🎯
Bonus Round: The Power Moves 💪
Middleware Parameters:
// Define parameter in middleware
public function handle(Request $request, Closure $next, $role)
{
if (!auth()->user()->hasRole($role)) {
abort(403);
}
return $next($request);
}
// Use it in routes
Route::get('/admin', [AdminController::class, 'index'])
->middleware('role:admin');
Route::get('/moderator', [ModController::class, 'index'])
->middleware('role:moderator');
Global Middleware:
Add to $middleware array in Kernel.php to run on EVERY request:
protected $middleware = [
\App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\LogAllRequests::class, // Your custom one!
];
Terminable Middleware:
Run code AFTER the response is sent to the user:
class CalculateMetrics
{
public function handle(Request $request, Closure $next)
{
return $next($request);
}
public function terminate(Request $request, $response)
{
// Response already sent! User doesn't wait for this
DB::table('metrics')->insert([
'endpoint' => $request->path(),
'response_time' => microtime(true) - LARAVEL_START,
'status' => $response->status()
]);
}
}
The Middleware Checklist ✅
Make your app secure and clean:
- Use middleware for auth checks (not in controllers)
- Add rate limiting to public APIs
- Implement role-based access control
- Add security headers to responses
- Log important requests/responses
- Transform data at the boundary (snake_case → camelCase)
Real Talk 💬
Q: "Should I put business logic in middleware?"
A: NO! Middleware is for request/response filtering. Business logic goes in controllers, services, or actions.
Q: "Can middleware access the database?"
A: Yes, but be careful! You don't want slow middleware blocking every request. Cache when possible!
Q: "How many middleware is too many?"
A: If your route has 10+ middleware, something's wrong. Group related checks together.
Q: "Global vs Route middleware?"
A: Global for stuff like CORS, security headers. Route middleware for auth, permissions, rate limits.
The Bottom Line
Middleware is your app's security guard, traffic cop, and personal assistant all in one:
- Protect routes (auth, roles, permissions)
- Rate limit (stop abuse)
- Transform data (keep controllers clean)
- Add headers (security, versioning)
- Log stuff (debugging, analytics)
- Maintenance mode (selective downtime)
Stop writing the same checks in every controller. Let middleware do the boring work while you focus on the fun stuff!
Think of it this way: Would you check if someone's wearing shoes in every room of your house? No! You check at the door. Same with middleware! 🚪✨
Want to discuss middleware patterns? Connect on LinkedIn. I love talking Laravel!
Found this helpful? Star this blog on GitHub for more Laravel goodness!
Now go build some middleware magic! 🎩✨