LaravelPHPRequest LifecycleMiddlewareBackend

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.

Laravel Lifecycle — How a Request Becomes a Response

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 in boot(). 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.

  1. Front controller — Apache/Nginx send every request to public/index.php. Composer autoload loads, then the app is created from bootstrap/app.php.
  2. Service providers — every provider register()s bindings first. After that, every provider boot()s. Config, events, routes, and gates show up here.
  3. HTTP kernel — Laravel 10 uses app/Http/Kernel.php. Laravel 11+ folds a lot of this into bootstrap/app.php, but the job is the same: global middleware, then group middleware (web / api), then route middleware.
  4. Router — method + URI match a route. Route model binding runs. If nothing matches, you get a 404 before any controller code.
  5. Controller / closure — your app logic. Return a view, JSON, redirect, or a stream. Laravel wraps that return value into a Response.
  6. 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:

  1. All register() methods — bind interfaces, merge config. Do not resolve other providers’ services here if you can avoid it.
  2. 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
  • Groupweb: cookies, session, CSRF, SubstituteBindings. api: throttle, bindings, Sanctum stateful check if you added it
  • Routeauth, 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.php makes 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 in boot() — don’t resolve sibling services too early.
  • Put request checks in middleware or Form Requests, not in a 40-line controller constructor.
  • Keep web and api groups 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 suspectdd() 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 to boot() or a deferred callback.
  • Debugging a 419 inside the controller → Fix: inspect CSRF / session middleware and the web group; the controller never ran.
  • API routes accidentally using the web stack → Fix: confirm routes/api.php prefix and middleware in bootstrap/app.php or RouteServiceProvider.
  • Custom middleware registered but not attached → Fix: alias it and add it to the route or group. Registration ≠ execution.
  • Assuming dd($request->user()) works before auth middleware → 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.php and leaves as a Response plus terminate().
  • Providers: register() then boot(). 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.