- Knowledge
- technology
- OOP
- Tips
- Programming
- Tips
- Tutorial
- SEO
- Ranking
- Knowledge
- Special Day
- Seo
- Bug
- Data science
- Seo
- artificial intelligence
- Machine Learning
- Robotics
- happyNewYear2021
- newYearEve
- 2021
- Automation
- Smart Home
- Career
- Best Practices
- Git
- Logging
- Web Fundamentals
- DNS
- HTTPS
- Performance
- AI Tools
- ChatGPT
- Claude
- Gemini
- Laravel
- Eloquent
- MySQL
- HTTPS
- TLS
- Web Security
- Certificates
- Developer Life
- Debugging
- Docker
- DevOps
- Transactions
- Queues
- LLMs
- AI
- AI Coding
- Developer Tools
- React Native
- Expo
- Kate PMS
- Mobile Apps
- Laravel
- Authentication
- Sanctum
- Cookies
- API Design
- Payments
- Idempotency
- DeepSeek
- Open Source AI
- LLMs
- AI News
- Git
- Version Control
- AI Coding
- Prompting
- PHP
- Checklist
- MCP
- AI Agents
- OpenAI
- Architecture
- Microservices
- Modular Monolith
- Estimation
- Developer Life
- Project Planning
- Humour
- OAuth
- OpenID Connect
- Authentication
- Embeddings
- Vector Search
- RAG
- pgvector
- OpenAI
- GPT-4.1
- Codex CLI
- Events
- Testing
- Clean Code
- Maintainability
- Code Review
- Webhooks
- API
- Security
- Claude Code
- Workflow
- AI
- LLM
- Prompt Injection
- Mobile
- React
- Networking
- TCP
- UDP
- HTTP/3
- CLAUDE.md
- AWS
- Cloud Security
- Backups
- PHPUnit
- Software Engineering
- Leadership
- Communication
- RAG
- Embeddings
- AI Engineering
- IT Infrastructure
- Networking
- Access Control
- CI/CD
- GitHub Actions
- Gemini CLI
- Claude Code
- JavaScript
- Async/Await
- Node.js
- Promises
- Security
- Cryptography
- Passwords
- MySQL
- Database
- Vibe Coding
- Software Quality
- DNS
- Code Reading
- Onboarding
- Productivity
- Background Jobs
- Developer Humour
- Estimates
- Dev Life
- JWT
- o3-mini
- DeepSeek R1
- Rate Limiting
- Kate PMS
- E-Signing
- Audit Trail
- REST
- GraphQL
- API Design
- Laravel 12
- Upgrade Guide
- Open Source
- Self-Hosting
- Task Scheduling
- Cron
- Secrets
- CORS
- PHP
- PHP-FPM
- OPcache
- GitHub Copilot
- Software Architecture
- Engineering
- TypeScript
- JavaScript
- Type Safety
- AI Security
- React Native
- Product Design
- AI Agents
- Kiro
- Queues
- Redis
- RabbitMQ
- AWS SQS
- Nginx
- Apache
- GPT-5
- gpt-oss
- Clean Code
- Architecture
- Naming
- Documentation
- Career
- ADR
- Teamwork
- Supply Chain
- Kate HRM
- HR Software
- Permissions
- System Design
- Pagination
- SSH
- Linux
- Big O
- Databases
- Laravel Boost
- MCP
- Developer Skills
- Validation
- Databases
- Indexes
- Code Quality
- Deployment
- Developer Humour
- Feature Flags
- Code Review
- Pull Requests
- Docker
- Cursor
- Authorization
- RBAC
- Gemini
- Long Context
- PHP 8.4
- Caching
- Dependency Injection
- Web Performance
- Browser
- CSS
- Database
- Migrations
- ChatGPT
- AI for Developers
- Monitoring
- On-Call
- REST
- Backend
- SQL
- NoSQL
- Database Design
- Coding Agents
- Claude 4
- API Resources
- REST API
- Load Balancing
- Scaling
- AWS
- AI Tools
- Claude
- Sora 2
- CTE
- 2FA
- TOTP
- Programming Languages
- Prompts
- Developer Workflow
- API Gateway
- APIs
- Passport
- API Auth
- Learning
- Burnout
- Developer Growth
- Web Development
- SEO
- Kate Mall
- ChatGPT Atlas
- Agent Skills
- Middleware
- Laravel 12
- Collections
- Context Window
- Monitoring
- Commit Messages
- Self Review
- Growth
- Regex
- Programming Basics
- Text Processing
- Database Design
- Normalization
- Linux
- Server Security
- Linux Foundation
- Open Standards
- Legacy Code
- Documentation
- AI Workflow
- File Uploads
- Test Data
- Hashing
- Performance
- Caching
- Enums
- Scope Creep
- Estimation
- Codex
- Gemini CLI
- Timezones
- Carbon
- Bugs
- PHP 8.5
- Gemini 3
- GPT-5.1
- Data Integrity
- Event Loop
- Async
- Opus 4.5
- AI Models
- React
- Forms
- Frontend
- Backups
- AI Images
- DALL-E
- Midjourney
- Race Conditions
- Concurrency
- Legacy Code
- Refactoring
- Senior Engineer
- Scope
- LLM
- CDN
- Web
- Sub-Agents
- Soft Deletes
- Audit Log
- Concurrency
- AI Learning
- NestJS
- AI Evals
- Policies
- SPF DKIM DMARC
- Unicode
- UTF-8
- Knowledge Graph
- Value Objects
- Technical Debt
- Feature Flags
- Laravel Pennant
- Deployment
- Copilot
- Composer
- Dependencies
- Artisan
- Automation
- AWS S3
- Object Storage
- Cloud
- Small Language Models
- Ollama
- Production
- Sessions
- HTTP
- Mentoring
- SQL
- Virtual Machines
- Web Development
- HTTP/2
- QUIC
- Web Performance
- AI Integration
- LLM API
- SOLID
- OOP
- Hosting
- Serverless
- Merge Conflicts
- Temperature
- AI Development
- Reverse Proxy
- Nginx
- Infrastructure
- Verification
- Passkeys
- WebAuthn
- Teams
- Communication
- Stakeholders
- Monorepo
- CI/CD
- Versioning
- JSON Schema
- Livewire
- Inertia
- Meetings
- Distributed Systems
- Privacy
- Full-Stack
- T-Shaped Skills
- Money
- Notifications
- Web Security
- HTTP Headers
- CSP
- Function Calling
- Load Testing
- k6
- Data Extraction
- Debugging
- WebSockets
- SSE
- Real-Time
- Laravel Reverb
- Infrastructure as Code
- Terraform
- Side Projects
- Laravel Pint
- OpenAPI
- Swagger
- UX
- Multimodal
- Jest
- Pair Programming
- APIs
- Rate Limiting
- Resilience
- Dev Humour
- Design Tokens
- JWT
- API Keys
- Sessions
- PHPStan
- Rector
- Incidents
- Reporting
- Dashboards
- Zero Trust
- IAM
- Search
- Laravel Scout
- Junior Developers
- Mentoring
- Images
- WebP
- AVIF
- Bug Reports
- Let's Encrypt
- Design Docs
- Software Design
- Observers
- Replication
- Accountability
- Data Structures
- Reliability
- LLM Memory
- Error Handling
- Payments
- Payment Gateway
- Webhooks
- PCI DSS
- Observability
- OpenTelemetry
- Personal Brand
- Writing
- Conventions
- Dates
- Scheduling
- Disaster Recovery
- Compression
- Brotli
- Deadlines
- Developer Habits
- State Machines
- Tech Roles
- UUID
- ULID
- Horizon
- Planning
- Engineering Culture
- Ownership
- Soft Skills
- Socialite
- Cost Control
- Collations
- Unicode
- Octane
- PostgreSQL
Laravel Middleware Explained With Real Use Cases
About Post
Every request to your Laravel app walks through a series of doors before it reaches your controller. Is the app in maintenance mode? Is this a valid session? Is the user logged in? Have they made too many requests this minute?
Most developers use those doors every day (auth, throttle) without ever building one. That's a shame, because middleware is one of the cleanest tools Laravel gives you. Once you see how it works, you start noticing code in your controllers that really belongs at the door.
The mental model: layers of an onion
Picture the request travelling inward through layers, reaching your controller at the centre, and the response travelling back out through the same layers in reverse.
Request → [ maintenance ] → [ session ] → [ auth ] → Controller
Response ← [ maintenance ] ← [ session ] ← [ auth ] ← Controller
Each layer can do three things: let the request through, stop it and return a response itself (a 401, a redirect), or let it through and change the response on the way back. That's all middleware is. Laravel calls this chain the pipeline.
Anatomy of a middleware
Create one with php artisan make:middleware SetLocale. Every middleware has a handle method that receives the request and a $next closure, which means "pass it to the next layer":
class SetLocale
{
public function handle(Request $request, Closure $next): Response
{
$locale = $request->user()?->locale
?? $request->getPreferredLanguage(['en', 'ar']);
App::setLocale($locale);
return $next($request);
}
}
This is a before middleware: it does its work, then hands the request on. Logged-in users get their saved language; everyone else gets the best match from their browser's Accept-Language header. Your controllers, views and validation messages never have to think about it again.
Before vs after
The position of $next($request) decides when your code runs. Code before it runs on the way in. Code after it runs on the way out, with the response in hand:
public function handle(Request $request, Closure $next): Response
{
$response = $next($request);
$response->headers->set('X-Content-Type-Options', 'nosniff');
$response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin');
return $response;
}
This after middleware adds security headers to every response, whatever the controller returned. One place, every page, impossible to forget on a new route.
Registering middleware in Laravel 12
Apps created on Laravel 11 or later have no HTTP Kernel.php. You register middleware in bootstrap/app.php:
->withMiddleware(function (Middleware $middleware) {
// add to every request in the "web" group
$middleware->web(append: [SetLocale::class]);
// short names for use on routes
$middleware->alias([
'role' => EnsureUserHasRole::class,
'partner.key' => EnsureValidPartnerKey::class,
]);
})
There are three levels to choose from:
- Global (
$middleware->append(...)): every request, web and API. Use sparingly. - Groups (
web,api): every route in that group. Thewebgroup already handles cookies, sessions and CSRF. - Route: just the routes that need it, via
->middleware('role:admin')or a route group.
Pick the narrowest level that works. A global middleware that queries the database runs on every request, including health checks and asset fallbacks.
Parameters: one middleware, many rules
Middleware can take arguments after a colon. Variadic parameters make role checks neat:
public function handle(Request $request, Closure $next, string ...$roles): Response
{
abort_unless(in_array($request->user()?->role, $roles, true), 403);
return $next($request);
}
// routes/web.php
Route::middleware(['auth', 'role:admin,manager'])->group(function () {
Route::get('/reports', [ReportController::class, 'index']);
});
Notice auth comes first. The role check assumes a user exists, so it must run after authentication. Order matters.
A role check at the route level is a coarse gate. Whether a manager can edit this particular record still belongs in a policy.
Real use case: API keys for a partner integration
Say a partner system calls a few endpoints with a shared key. Middleware keeps that check off your controllers:
public function handle(Request $request, Closure $next): Response
{
$expected = (string) config('services.partner.key');
$given = $request->header('X-Partner-Key');
if ($expected === '' || ! is_string($given) || ! hash_equals($expected, $given)) {
abort(401);
}
return $next($request);
}
Two details worth copying. hash_equals compares in constant time, so response timing doesn't leak how much of the key was right. And the $expected === '' check matters more than it looks: without it, a missing config value plus an empty header would compare as equal and let anyone in. (Simplified: for real partner APIs, consider per-partner keys stored hashed, plus rate limiting.)
Real use case: closing one module for maintenance
Laravel's built-in php artisan down (with --secret for a bypass link) takes the whole app offline. Sometimes you only need to pause one area, say online payments while you switch gateway settings:
public function handle(Request $request, Closure $next): Response
{
if (! config('features.payments_enabled')) {
return response()->view('maintenance.payments', status: 503);
}
return $next($request);
}
The rest of the app keeps working, and users get a clear message instead of a half-broken checkout.
Terminable middleware: work after the response is sent
A middleware can also have a terminate method, which runs after the response has been sent to the browser:
public function terminate(Request $request, Response $response): void
{
Log::info('request.done', [
'path' => $request->path(),
'status' => $response->getStatusCode(),
'ms' => round((microtime(true) - LARAVEL_START) * 1000),
]);
}
Two gotchas. "After the response is sent" depends on your server: with PHP-FPM the connection is closed first, but on other setups the user may still wait. And Laravel resolves a fresh instance for terminate unless you register the middleware as a singleton, so don't rely on properties set in handle. For anything heavy, a queued job is still the better tool.
What belongs in middleware: concerns about the request itself, applied the same way across many routes. Locale, headers, authentication, API keys, feature switches, logging. What doesn't: business logic that depends on what the controller is doing. If your middleware needs to know which invoice is being paid, it's in the wrong place.
Quick reference
- Code before
$next($request)runs on the way in; code after it runs on the way out. - Return a response early to stop the request.
- Register in
bootstrap/app.php: global, group or route alias. - Use parameters (
role:admin,manager) instead of near-duplicate classes. - Mind the order: authentication before anything that needs a user.
- Keep middleware fast; it runs on every matching request.
What's the most useful custom middleware in your project? I'm always looking for good ideas to steal.

Be first to comment it...