Laravel Tutorial · Requests and Responses · Lesson 11 of 27
Laravel Middleware
Run code before and after requests: authentication checks, logging, headers and rate limits. Write your own middleware and apply it to routes and groups.
What middleware does
Middleware sits between the incoming request and your route, like layers of an onion. Each layer can:
- stop the request (return a 401 or redirect) before it reaches the route,
- change the request (add data),
- change the response on the way out (add headers).
Laravel ships with middleware for authentication (auth), throttling (throttle:60,1), signed URLs, CSRF protection and more. Create your own with php artisan make:middleware EnsureApiKey; a middleware has a handle($request, Closure $next) method that calls $next($request) to continue.
Apply middleware to a route with ->middleware(...), to a group with Route::middleware([...])->group(...), or globally in bootstrap/app.php with ->withMiddleware(...).
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
class EnsureApiKey
{
public function handle(Request $request, Closure $next)
{
if ($request->header('X-Api-Key') !== 'secret-key') {
return response()->json(['message' => 'Invalid API key'], 401); // stop here
}
$request->attributes->set('client', 'mobile-app'); // pass data along
return $next($request);
}
}
class AddHandledByHeader
{
public function handle(Request $request, Closure $next)
{
$response = $next($request); // the route runs here
$response->headers->set('X-Handled-By', 'laravel');
return $response; // change the response on the way out
}
}
Route::middleware([AddHandledByHeader::class, EnsureApiKey::class])->group(function () {
Route::get('/reports', fn (Request $request) => ['client' => $request->attributes->get('client')]);
});
Route::get('/ping', fn () => 'pong')->middleware('throttle:2,1');
visit('GET', '/reports');
$response = visit('GET', '/reports', [], ['X-Api-Key' => 'secret-key']);
echo 'X-Handled-By: ', $response->headers->get('X-Handled-By'), "\n";
foreach (range(1, 3) as $i) {
visit('GET', '/ping');
}
Output
GET /reports -> 401 {"message":"Invalid API key"}
GET /reports -> 200 {"client":"mobile-app"}
X-Handled-By: laravel
GET /ping -> 200 pong
GET /ping -> 200 pong
GET /ping -> 429 {
"message": "Too Many Attempts."
}Run this on your computer — see “Get Started” for setup.
Rate limiting: ->middleware('throttle:3,1') allows 3 requests per minute per user/IP and answers 429 after that, with Retry-After headers.
Test yourself
In middleware, what does calling $next($request) do?
Skips the routePasses the request to the next layer (eventually the route) and returns its responseRestarts the requestLogs the request
Show answer
Answer: Passes the request to the next layer (eventually the route) and returns its response