Laravel Lifecycle — How a Request Becomes a Response
The Laravel lifecycle is the path from HTTP request to response: bootstrap, service providers, middleware, routing, then the controller. Know that path and most “random” bugs stop being random.

Quick Answer
The Laravel lifecycle is the ordered path an HTTP request takes: public/index.php boots the app, service providers register and boot, the HTTP kernel runs middleware, the router matches a route, a controller (or closure) runs, then Laravel sends a response and fires terminate callbacks. If you know that order, 404s, 419 CSRF errors, and “middleware never ran” bugs become easier to locate.
Quick Facts
Topic: Laravel Lifecycle
Category: Backend / Laravel Internals
Table of Contents
- What Is Laravel Lifecycle?
- Why It Matters
- How It Works
- Step-by-Step Guide
- Real Example
- Pros & Cons
- Best Practices
- Common Mistakes
- FAQs
- Key Takeaways
What Is Laravel Lifecycle?
Laravel lifecycle means the request-to-response pipeline. A browser hits /login, PHP starts at public/index.php, Laravel builds the application container, and the request walks through kernel, middleware, route, and controller before HTML or JSON comes back.
People also say “application lifecycle” for artisan commands. Same idea: bootstrap, providers, then the job. This post sticks to HTTP, because that’s where most production issues show up.
If you ship Laravel APIs with Vue or Nuxt, this pipeline is why Sanctum CSRF cookies, CORS, and auth:sanctum fail in specific spots—not “everywhere.” See Laravel Sanctum API authentication once the request path is clear.
Why It Matters
- Faster debugging — a 419 is CSRF middleware; a missing user is auth middleware; a blank 404 is routing. You stop guessing.
- Safer changes — you know whether a check belongs in a provider, global middleware, a route group, or the controller.
- Correct boot order — bindings belong in
register(); using other services belongs inboot(). Mix those and you get circular resolve errors. - Predictable APIs — SPA cookies, JSON exceptions, and CORS all attach at known layers of the same pipeline.
How It Works
Conceptually Laravel does one thing: turn an Illuminate\Http\Request into an Illuminate\Http\Response. The layers in between are fixed.
- Front controller — Apache/Nginx send every request to
public/index.php. Composer autoload loads, then the app is created frombootstrap/app.php. - Service providers — every provider
register()s bindings first. After that, every providerboot()s. Config, events, routes, and gates show up here. - HTTP kernel — Laravel 10 uses
app/Http/Kernel.php. Laravel 11+ folds a lot of this intobootstrap/app.php, but the job is the same: global middleware, then group middleware (web/api), then route middleware. - Router — method + URI match a route. Route model binding runs. If nothing matches, you get a 404 before any controller code.
- Controller / closure — your app logic. Return a view, JSON, redirect, or a stream. Laravel wraps that return value into a Response.
- Outbound middleware + terminate — the response walks back through middleware. Then
terminate()hooks run (session persist, logging) after the response is sent.
Artisan is a sibling path: artisan boots the same app, then the Console kernel dispatches a command. No HTTP middleware unless you fake a request.
Step-by-Step Guide
Walk a real POST /login through the stack. Names differ slightly between Laravel 10 and 11; the order does not.
Step 1: Hit the front controller
public/index.php is the only public PHP entry. It requires Composer autoload, creates the application, then asks the HTTP kernel to handle the request.
// public/index.php (shape, not a copy-paste of every Laravel version)
require __DIR__.'/../vendor/autoload.php';
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle($request = Illuminate\Http\Request::capture());
$response->send();
$kernel->terminate($request, $response);
If this file is missing from the web root, you get directory listings or “index of /” — not a Laravel 404.
Step 2: Bootstrap the application
bootstrap/app.php returns the Application container. Laravel 11+ also registers routing, middleware, and exception handling here:
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
//
})
->withExceptions(function (Exceptions $exceptions) {
//
})->create();
Until this returns, facades and app() helpers are not fully available. Don’t put business logic in index.php.
Step 3: Register, then boot service providers
Listed in bootstrap/providers.php (Laravel 11+) or config/app.php (older). Order:
- All
register()methods — bind interfaces, merge config. Do not resolve other providers’ services here if you can avoid it. - All
boot()methods — routes, view composers, gates, event listeners. The container is ready.
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(InvoiceNumberGenerator::class);
}
public function boot(): void
{
Gate::policy(Document::class, DocumentPolicy::class);
}
}
Deferred providers only boot when something actually resolves them. That’s why a listener “sometimes doesn’t exist” until a related class is used.
Step 4: Run the HTTP kernel and middleware
Kernel pipeline (mental model):
- Global — TrustProxies, HandleCors, PreventRequestsDuringMaintenance, TrimStrings
- Group —
web: cookies, session, CSRF, SubstituteBindings.api: throttle, bindings, Sanctum stateful check if you added it - Route —
auth,auth:sanctum,verified, custom aliases
Middleware can abort early. CSRF on a web POST without a token returns 419 before the controller. Auth middleware returns 401/redirect before your store() method.
Route::middleware('auth')->group(function () {
Route::post('/documents', [DocumentController::class, 'store']);
});
Step 5: Match the route and resolve the controller
Router looks at verb + URI + domain. Then:
- Implicit / explicit route model binding
- Form Request authorization + validation (
StoreDocumentRequest) - Controller method / invokable / closure
public function store(StoreDocumentRequest $request)
{
$document = Document::create($request->validated());
return redirect()->route('documents.show', $document);
}
If validation fails, Laravel never enters the method. That’s still the lifecycle — Form Request is middleware-adjacent, not “controller code.”
Step 6: Send the response and terminate
Whatever you return (array, model, view, redirect) is converted to a Response. Outbound middleware can still change headers (CORS, cookies). Then send() writes to the client, and terminate() runs: session write, queued cookies, some loggers.
Work that must finish after the browser already has the page belongs in dispatch()->afterResponse() or a queued job—not in the controller after return, because return already ended your method. Terminable middleware is the official “after send” hook.
Real-World Example
A Nuxt SPA posts login to Laravel on api.example.com. The user sees 419, then CORS errors, then an empty user object. Three bugs, three layers.
419: login route sat in web without /sanctum/csrf-cookie. CSRF middleware ran; controller never did.
CORS: HandleCors is global, but supports_credentials was false and the origin wasn’t allowed. Browser blocked the response after Laravel had already succeeded.
Empty user: /api/user used auth:sanctum, but the SPA didn’t send cookies (withCredentials). Sanctum ran, found no session and no Bearer token, returned 401. Frontend mapped that to {}.
Once the team drew the lifecycle on a whiteboard—bootstrap → CORS → stateful Sanctum → CSRF → session → route → controller—the fixes were one-liners, not a rewrite. Same pattern I use on Laravel + Nuxt work like Complere eQMS: locate the layer, then change that layer only.
Pros & Cons
Advantages
- One documented pipeline for every HTTP request
- Middleware and providers give clear extension points
- Same bootstrap for HTTP and artisan, so config/bindings stay consistent
- Early abort (CSRF, auth, maintenance) keeps controllers thin
Disadvantages
- Laravel 10 Kernel vs Laravel 11
bootstrap/app.phpmakes tutorials mismatch your repo - Too much logic in
AppServiceProvider::boot()slows every request - Middleware order is easy to get wrong (CORS vs cookies vs Sanctum)
- Magic (facades, implicit binding) hides the pipeline until something breaks
Best Practices
- Bind in
register(), use inboot()— don’t resolve sibling services too early. - Put request checks in middleware or Form Requests, not in a 40-line controller constructor.
- Keep
webandapigroups honest — sessions/CSRF on cookie SPAs; tokens on mobile. Mixing both on one route is how Sanctum “randomly” fails. - Log or dump at the layer you suspect —
dd()in a controller is useless if middleware already returned 419. - Don’t boot heavy work globally — third-party SDKs belong in deferred providers or the class that needs them.
- Treat terminate as after-response — don’t assume the client waited for your extra queries.
Common Mistakes
- Calling other services in
register()→ Fix: move that code toboot()or a deferred callback. - Debugging a 419 inside the controller → Fix: inspect CSRF / session middleware and the
webgroup; the controller never ran. - API routes accidentally using the
webstack → Fix: confirmroutes/api.phpprefix and middleware inbootstrap/app.phporRouteServiceProvider. - Custom middleware registered but not attached → Fix: alias it and add it to the route or group. Registration ≠ execution.
- Assuming
dd($request->user())works beforeauthmiddleware → Fix: user is resolved when auth middleware (or guard) runs, not at bootstrap. - Heavy queries in a service provider
boot()→ Fix: lazy-load; providers run on every request, including health checks.
Frequently Asked Questions
What is the Laravel lifecycle?
It is the sequence Laravel follows to handle a request: load autoload, create the app, boot service providers, run HTTP kernel middleware, match a route, execute a controller, send a response, then terminate. Artisan uses the same bootstrap with the console kernel.
How do I trace a Laravel request step by step?
Start at public/index.php, then bootstrap/app.php, providers, middleware groups, routes/web.php or routes/api.php, then the controller. Laravel Debugbar, Telescope, or a temporary log in middleware will show where the request died.
Laravel lifecycle vs middleware — what’s the difference?
The lifecycle is the whole path. Middleware is one layer inside it, sitting between the kernel and the router/controller. Middleware can stop the lifecycle early or change the response on the way out.
Is learning the Laravel lifecycle worth it?
Yes if you debug auth, CSRF, sessions, or packages. You don’t need to memorize Illuminate source. You do need the order: providers → middleware → route → controller → terminate.
Where do service providers run in the lifecycle?
After the application is created and before the kernel handles the request. All register() methods run first, then all boot() methods. Route files are typically loaded from a provider or withRouting() during that boot phase.
Summary
Laravel lifecycle is not abstract theory. It is the ordered list of files and layers that turn a request into a response: index.php, application bootstrap, service providers, kernel middleware, routing, controller, then terminate.
Put bindings in register(), app wiring in boot(), cross-cutting checks in middleware, and business rules in controllers or actions. When something fails, ask which layer ran last—not which tutorial you copied.
Next: pick one failing request in your app, write the six steps beside it, and fix only that layer. If you want this mapped onto a Laravel + Nuxt stack, get in touch.
Key Takeaways
- Every HTTP request enters through
public/index.phpand leaves as a Response plusterminate(). - Providers:
register()thenboot(). Don’t reverse that. - Middleware can abort before your controller — 419, 401, 403 usually mean this.
- Router match + Form Requests sit between middleware and controller code.
- Debug by layer (bootstrap, middleware, route, controller), not by stacking more dumps in the action.
Comments
0 comments · new ones appear after approval
No comments yet. Be the first to share your thoughts.
Leave a comment
Your comment will be reviewed before it appears.