Laravel Eloquent ORM: What is it and Why is it a Game-Changer?
Discover the power of Eloquent ORM for database interactions.
Middleware acts as a filter for HTTP requests entering your application. Think of it as a series of security checkpoints that requests must pass through before reaching your application logic, and responses must pass through before being sent back to the user.
HTTP Request
↓
Middleware 1 (e.g., Authenticate)
↓
Middleware 2 (e.g., Log Request)
↓
Middleware 3 (e.g., Check Permission)
↓
Your Application Logic (Controller)
↓
Middleware 3 (Modify Response)
↓
Middleware 2 (Log Response)
↓
Middleware 1 (Add Headers)
↓
HTTP Response
# Create a new middleware
php artisan make:middleware CheckUserAge
php artisan make:middleware AdminAccess
php artisan make:middleware LogRequests
<?php
// app/Http/Middleware/CheckUserAge.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class CheckUserAge
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next): Response
{
// Your middleware logic here
// This runs BEFORE the request is handled
$response = $next($request);
// This runs AFTER the request is handled
// You can modify the response here
return $response;
}
}
<?php
// app/Http/Middleware/CheckUserAge.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class CheckUserAge
{
/**
* Handle an incoming request.
*/
public function handle(Request $request, Closure $next, $minAge = 18): Response
{
$age = $request->input('age') ?? $request->user()->age ?? null;
if (!$age || $age < $minAge) {
// Redirect if age requirement not met
return redirect()->route('age.verification')
->with('error', "You must be at least {$minAge} years old to access this page.");
}
return $next($request);
}
}
<?php
// app/Http/Middleware/AdminAccess.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Facades\Auth;
class AdminAccess
{
/**
* Handle an incoming request.
*/
public function handle(Request $request, Closure $next, $role = 'admin'): Response
{
// Check if user is authenticated
if (!Auth::check()) {
return redirect()->route('login')
->with('error', 'Please log in to access this page.');
}
// Check if user has the required role
$user = Auth::user();
if (!$user->hasRole($role)) {
// If API request, return JSON response
if ($request->expectsJson()) {
return response()->json([
'error' => 'Unauthorized. Admin access required.'
], 403);
}
// For web requests, redirect with error
return redirect()->route('home')
->with('error', 'You do not have permission to access this area.');
}
return $next($request);
}
}
<?php
// app/Http/Middleware/LogRequests.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Facades\Log;
class LogRequests
{
/**
* Handle an incoming request.
*/
public function handle(Request $request, Closure $next): Response
{
// Log the incoming request
Log::info('Incoming Request', [
'method' => $request->method(),
'url' => $request->fullUrl(),
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
'user_id' => auth()->id() ?? 'guest',
]);
// Process the request
$response = $next($request);
// Log the response
Log::info('Outgoing Response', [
'status' => $response->getStatusCode(),
'url' => $request->fullUrl(),
]);
return $response;
}
}
<?php
// app/Http/Middleware/ThrottleAPI.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Cache\RateLimiter;
use Illuminate\Support\Facades\Log;
class ThrottleAPI
{
protected $limiter;
public function __construct(RateLimiter $limiter)
{
$this->limiter = $limiter;
}
public function handle(Request $request, Closure $next, $maxAttempts = 60, $decayMinutes = 1): Response
{
$key = $this->resolveRequestSignature($request);
if ($this->limiter->tooManyAttempts($key, $maxAttempts)) {
Log::warning('API rate limit exceeded', [
'ip' => $request->ip(),
'endpoint' => $request->path(),
]);
return response()->json([
'error' => 'Too Many Attempts.',
'retry_after' => $this->limiter->availableIn($key),
], 429);
}
$this->limiter->hit($key, $decayMinutes * 60);
$response = $next($request);
// Add rate limit headers to response
return $this->addHeaders(
$response,
$maxAttempts,
$this->calculateRemainingAttempts($key, $maxAttempts)
);
}
protected function resolveRequestSignature($request)
{
return sha1(
$request->method() .
'|' . $request->server('SERVER_NAME') .
'|' . $request->path() .
'|' . $request->ip()
);
}
protected function addHeaders($response, $maxAttempts, $remainingAttempts)
{
$response->headers->add([
'X-RateLimit-Limit' => $maxAttempts,
'X-RateLimit-Remaining' => $remainingAttempts,
]);
return $response;
}
protected function calculateRemainingAttempts($key, $maxAttempts)
{
return $maxAttempts - $this->limiter->attempts($key);
}
}
<?php
// app/Http/Kernel.php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array<int, class-string|string>
*/
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Illuminate\Http\Middleware\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
// Global custom middleware
\App\Http\Middleware\LogRequests::class,
];
/**
* The application's route middleware groups.
*
* @var array<string, array<int, class-string|string>>
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
\Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
// Custom API middleware
\App\Http\Middleware\ThrottleAPI::class,
],
];
/**
* The application's route middleware.
*
* These middleware may be assigned to groups or used individually.
*
* @var array<string, class-string|string>
*/
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
// Custom middleware
'age.check' => \App\Http\Middleware\CheckUserAge::class,
'admin' => \App\Http\Middleware\AdminAccess::class,
'log.requests' => \App\Http\Middleware\LogRequests::class,
'api.throttle' => \App\Http\Middleware\ThrottleAPI::class,
];
}
// routes/web.php
use App\Http\Controllers\AdminController;
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\ContentController;
// Single middleware
Route::get('/adult-content', [ContentController::class, 'adult'])
->middleware('age.check');
// Multiple middleware
Route::get('/admin/dashboard', [AdminController::class, 'dashboard'])
->middleware(['auth', 'admin']);
// Middleware with parameters
Route::get('/alcohol-content', [ContentController::class, 'alcohol'])
->middleware('age.check:21'); // Pass 21 as parameter
// Middleware group
Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
});
// API routes with custom throttling
Route::middleware(['api.throttle:100,1'])->group(function () {
Route::get('/api/posts', [PostController::class, 'index']);
Route::post('/api/posts', [PostController::class, 'store']);
});
<?php
// app/Http/Controllers/AdminController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class AdminController extends Controller
{
public function __construct()
{
// Apply to all methods
$this->middleware('auth');
// Apply to specific methods
$this->middleware('admin')->only(['dashboard', 'users']);
$this->middleware('log.requests')->only('dashboard');
// Apply to all except specified methods
$this->middleware('verified')->except(['settings']);
// Middleware with parameters
$this->middleware('age.check:21')->only('alcoholContent');
}
public function dashboard()
{
return view('admin.dashboard');
}
public function users()
{
return view('admin.users');
}
public function alcoholContent()
{
return view('content.alcohol');
}
public function settings()
{
return view('admin.settings');
}
}
<?php
// app/Http/Middleware/PerformanceMonitor.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Facades\Log;
class PerformanceMonitor
{
protected $startTime;
public function handle(Request $request, Closure $next): Response
{
$this->startTime = microtime(true);
return $next($request);
}
public function terminate($request, $response): void
{
$endTime = microtime(true);
$executionTime = round(($endTime - $this->startTime) * 1000, 2); // Convert to milliseconds
// Log slow requests
if ($executionTime > 500) { // More than 500ms
Log::warning('Slow Request Detected', [
'url' => $request->fullUrl(),
'method' => $request->method(),
'execution_time' => $executionTime . 'ms',
'memory_usage' => round(memory_get_peak_usage(true) / 1024 / 1024, 2) . 'MB',
]);
}
// Log all requests for analytics (in production, consider queuing this)
Log::channel('performance')->info('Request Performance', [
'method' => $request->method(),
'url' => $request->path(),
'execution_time' => $executionTime,
'status_code' => $response->getStatusCode(),
'user_agent' => $request->userAgent(),
]);
}
}
<?php
// app/Http/Middleware/FeatureFlag.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class FeatureFlag
{
public function handle(Request $request, Closure $next, $feature): Response
{
if (!$this->isFeatureEnabled($feature)) {
if ($request->expectsJson()) {
return response()->json([
'error' => 'This feature is currently unavailable.'
], 503);
}
abort(503, 'This feature is currently unavailable.');
}
return $next($request);
}
protected function isFeatureEnabled($feature): bool
{
$features = [
'new_design' => env('FEATURE_NEW_DESIGN', false),
'beta_features' => env('FEATURE_BETA', false),
'payment_v2' => env('FEATURE_PAYMENT_V2', true),
];
return $features[$feature] ?? false;
}
}
<?php
// app/Http/Middleware/Localization.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Session;
class Localization
{
/**
* Handle an incoming request.
*/
public function handle(Request $request, Closure $next): Response
{
// Check URL parameter first (e.g., /en/home)
if ($request->has('lang')) {
$locale = $request->get('lang');
Session::put('locale', $locale);
}
// Check session
elseif (Session::has('locale')) {
$locale = Session::get('locale');
}
// Check browser language
else {
$locale = $request->getPreferredLanguage(['en', 'es', 'fr', 'de']);
}
// Validate and set locale
if (in_array($locale, ['en', 'es', 'fr', 'de'])) {
App::setLocale($locale);
} else {
App::setLocale('en'); // Default fallback
}
return $next($request);
}
}
<?php
// app/Http/Middleware/VerifyCsrfToken.php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array<int, string>
*/
protected $except = [
'stripe/*',
'webhook/*',
'api/*', // If you're building API that doesn't need CSRF
];
}
<?php
// app/Http/Middleware/Cors.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class Cors
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$response->headers->set('Access-Control-Allow-Origin', '*');
$response->headers->set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
$response->headers->set('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With');
$response->headers->set('Access-Control-Max-Age', '86400'); // 24 hours
// Handle preflight requests
if ($request->getMethod() === 'OPTIONS') {
$response->setStatusCode(200);
}
return $response;
}
}
<?php
// app/Http/Middleware/XSSProtection.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class XSSProtection
{
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
// Add security headers
$response->headers->set('X-XSS-Protection', '1; mode=block');
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('X-Frame-Options', 'DENY');
$response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
return $response;
}
}
<?php
// tests/Unit/Middleware/AdminAccessTest.php
namespace Tests\Unit\Middleware;
use Tests\TestCase;
use App\Http\Middleware\AdminAccess;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class AdminAccessTest extends TestCase
{
public function test_redirects_guests_to_login()
{
// Create a request
$request = Request::create('/admin', 'GET');
// Create middleware instance
$middleware = new AdminAccess();
// Get response
$response = $middleware->handle($request, function () {});
// Assert redirection to login
$this->assertEquals(302, $response->getStatusCode());
$this->assertStringContainsString('login', $response->getTargetUrl());
}
public function test_allows_admin_users()
{
// Create admin user
$user = User::factory()->admin()->create();
// Authenticate user
Auth::login($user);
$request = Request::create('/admin', 'GET');
$middleware = new AdminAccess();
$called = false;
$response = $middleware->handle($request, function () use (&$called) {
$called = true;
return response('OK');
});
$this->assertTrue($called);
$this->assertEquals('OK', $response->getContent());
}
public function test_denies_non_admin_users()
{
$user = User::factory()->create(); // Regular user
Auth::login($user);
$request = Request::create('/admin', 'GET');
$middleware = new AdminAccess();
$response = $middleware->handle($request, function () {});
$this->assertEquals(302, $response->getStatusCode());
$this->assertStringContainsString('home', $response->getTargetUrl());
}
public function test_returns_json_for_api_requests()
{
$user = User::factory()->create();
Auth::login($user);
$request = Request::create('/admin', 'GET');
$request->headers->set('Accept', 'application/json');
$middleware = new AdminAccess();
$response = $middleware->handle($request, function () {});
$this->assertEquals(403, $response->getStatusCode());
$this->assertJson($response->getContent());
$this->assertStringContainsString('Unauthorized', $response->getContent());
}
}
<?php
// app/Http/Middleware/RedirectIfAuthenticated.php
namespace App\Http\Middleware;
use App\Providers\RouteServiceProvider;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class RedirectIfAuthenticated
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next, string ...$guards): Response
{
$guards = empty($guards) ? [null] : $guards;
foreach ($guards as $guard) {
if (Auth::guard($guard)->check()) {
$user = Auth::guard($guard)->user();
// Redirect based on user role
if ($user->is_admin) {
return redirect()->route('admin.dashboard');
}
if ($user->hasRole('vendor')) {
return redirect()->route('vendor.dashboard');
}
return redirect(RouteServiceProvider::HOME);
}
}
return $next($request);
}
}
<?php
// app/Http/Middleware/CheckForMaintenanceMode.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class CheckForMaintenanceMode
{
public function handle(Request $request, Closure $next): Response
{
// Check if maintenance mode is enabled
if (app()->isDownForMaintenance()) {
// Allow access for admins even during maintenance
if ($request->user() && $request->user()->is_admin) {
return $next($request);
}
// Allow access to specific IPs (your team)
$allowedIPs = ['192.168.1.100', '10.0.0.50'];
if (in_array($request->ip(), $allowedIPs)) {
return $next($request);
}
// Show maintenance page for everyone else
return response()->view('maintenance', [], 503);
}
return $next($request);
}
}
Middleware are filters that process HTTP requests before they reach your application logic. They can perform tasks like authentication, logging, CORS handling, and more. Requests pass through middleware stacks in order, and responses pass back through them in reverse order.
Global middleware runs on every request to your application, while route middleware only runs on specific routes where they're assigned. Global middleware is good for tasks like logging or CORS, while route middleware is perfect for authentication or authorization.
Pass parameters after the middleware name separated by colons: ->middleware('role:admin,editor'). In the middleware, access them as additional parameters in the handle method: public function handle($request, $next, ...$roles).
Terminable middleware runs after the response has been sent to the browser. It's useful for tasks that don't need to block the response, like logging or cleanup operations that can happen after the user receives their response.
Laravel has built-in CORS support via the cors middleware. You can configure allowed origins, methods, and headers in the config/cors.php file. For custom requirements, you can create your own CORS middleware.
$middlewareGroups allows you to group multiple middleware together under a single name, like 'web' or 'api'. This makes it easier to apply common middleware stacks to routes without listing each middleware individually.
// Authentication pattern
Route::middleware(['auth', 'verified'])->group(function () {
// Protected routes
});
// API pattern
Route::middleware(['auth:sanctum', 'api.throttle'])->group(function () {
// API routes
});
// Admin pattern
Route::middleware(['auth', 'admin'])->prefix('admin')->group(function () {
// Admin routes
});
// Localization pattern
Route::middleware(['localization'])->group(function () {
// Localized routes
});
You've now mastered Laravel Middleware and can effectively filter and process HTTP requests in your applications! In our next post, we'll explore Laravel Form Validation: Ensuring Data Integrity with Simple Rules to learn how to validate user input effectively.