Skip to content
Laravel

How I Follow a Request Through Laravel

I follow a request from Laravel's entry point to the route handler and back through middleware. Knowing where it can stop gives me a better place to start debugging.

Jeffrey Davidson 6 min read

A request comes back with a redirect, and the controller never runs. Or the controller returns the right page, but a response header is missing. Both problems get easier to reason about when I know where the request has been.

That's the part of Laravel's request lifecycle I find useful. I don't need to memorize every framework method. I need to know what runs before my code, what can stop the request, and what still happens after my code returns.

Let's follow a normal HTTP request through Laravel 13. I'll use the path to a protected account page as a running example. It's an example, not a route from a particular project.

Want the request path and debugging checklist at a glance? Download the companion slides (PDF, 8 pages).

The Map I Keep in Mind

Six numbered stages show a request entering public/index.php, bootstrapping Laravel, passing through global middleware, matching a route, passing through route middleware, and reaching a controller or closure. A separate response path returns through route middleware and global middleware to the browser. Middleware can return early.

The usual HTTP path, simplified. The request goes in through the layers; the response comes back through middleware that was entered. Some requests stop before they reach the route handler.

Open the full-size request-flow reference card.

The card separates framework setup from the code that handles a route. That distinction matters. An Action, Query, or ViewModel might sit behind my controller, but those are choices I make about application structure. Laravel doesn't require those layers to handle a request.

1. The Web Server Hands It to Laravel

For a request handled by Laravel, the entry point is public/index.php. The web server sends the request there, and that small file loads Composer's autoloader and gets the application from bootstrap/app.php.

The current entry point on this site ends with:

$app->handleRequest(
    Request::capture()
);

That line hands Laravel a request object representing the incoming HTTP request. From there, the framework takes over.

There's a useful boundary here: a static image served directly by Nginx may never enter Laravel. If an image URL is broken, I check whether the server is serving a file or forwarding the request to the application before blaming a controller.

2. Laravel Gets the Application Ready

Laravel needs its configuration and services ready before application code can use them. The HTTP kernel runs the bootstrapping process, which includes loading configuration and preparing the application's service providers.

Providers register container bindings and boot services. That's where the application establishes how a dependency should be resolved or sets up a framework feature. It doesn't mean every service is doing its work already. Preparing the database services is different from querying an order.

In the current application structure, bootstrap/app.php also configures things such as routing, middleware, and exception handling. Laravel still has an HTTP kernel even when the application doesn't have its own app/Http/Kernel.php file.

I think of this stage as getting the application ready to answer the request. The business operation hasn't happened yet.

3. Global Middleware Runs Before Route Matching

The request passes through the global middleware pipeline on its way to the router. This is where application-wide concerns can inspect or change the request, continue processing, or return a response immediately.

The distinction I care about is scope. A global middleware applies across the application's normal HTTP pipeline. A middleware assigned to a particular route or route group belongs to the route pipeline we'll reach shortly.

On this site, I append AddSecurityHeaders to the global stack. Its response work happens after it lets the request continue. That makes it useful for explaining both directions of the trip, rather than treating middleware as a set of checks that only run on the way in.

Because route matching hasn't happened yet, I don't assume a global middleware can already read the matched route or its route-bound models. Placement has consequences.

4. The Router Finds the Matching Route

The router matches the request to a route using information such as the URL and HTTP method. A GET request and a POST request to the same path can lead to different handlers.

For our example, suppose a GET request to an account page matches a route that requires authentication. Finding that route tells Laravel which handler should run and which route middleware applies. It doesn't mean the handler has run.

If I'm debugging a request that seems to reach the wrong code, this is where I check the route definition and method. I don't start by rewriting the controller. First I make sure Laravel has a reason to call that controller at all.

5. Route Middleware Decides Whether to Continue

Laravel runs the matched route's middleware before executing its handler. This includes middleware applied through route groups, not just names written beside an individual route.

For a normal web route, the web group supplies concerns such as sessions and CSRF protection. Those aren't automatically global just because they are common. The route's middleware configuration matters.

In our protected-page example, authentication middleware can prevent an unauthenticated visitor from reaching the handler. A typical browser request is redirected to the login page. Requests expecting JSON can receive an unauthenticated response instead.

That's a complete response. The controller didn't fail to run. The middleware answered first.

The same distinction helps with route model binding and authorization middleware: work may happen, or the request may be rejected, before the first line of the controller method executes.

6. The Route Handler Does Its Work

If the request makes it through the route middleware, Laravel executes the controller method or route closure.

This is where my application decides what to do. A controller might delegate an operation to an Action, get data through a Query, and return a view. Another route might return JSON or a redirect. None of those response types requires a Blade template.

If the method depends on a Form Request, its resolution can validate and authorize the input before the method body runs. Reaching this stage isn't a guarantee that every line of my controller executes.

I keep the handler focused on its job. Understanding the framework's pipeline doesn't mean putting that whole pipeline in the controller.

The Response Comes Back Out

Returning from the controller isn't the end of the trip. Middleware that called the next layer can receive its response and do more work before returning it to the layer outside.

Using Global and Route for the two middleware stacks, a simplified trace looks like this:

Global: before
  Route: before
    Handler responds
  Route: after
Global: after

This is an illustration of execution order, not a log captured from the site. The important part is the nesting. The middleware entered first gets its response back last.

That is how a middleware can add a header to a response created deeper in the application. It doesn't need to build the page itself.

An early response still comes back through the outer middleware that was entered. Middleware further inside, and the route handler, never run if processing stopped before reaching them.

What This Map Leaves Out

I wouldn't use this card as a complete execution trace. Exceptions can change the path, and Laravel turns handled exceptions into responses. I also wouldn't assume that every statement after the next-layer call executes when an exception interrupts it.

The normal request handler sends the response and then runs termination work. Terminable middleware is a separate concern from modifying the response on its way out. When work becomes visible to the client also depends on the server runtime.

Octane adds another boundary: long-lived workers reuse a booted application. This card describes the usual request path; it doesn't mean every worker bootstraps from scratch for every request.

Those details matter when they're the problem I'm investigating. I leave them off the main picture so the usual path is still readable.

Start With Where the Request Stopped

When a request behaves strangely, my first question is: how far did it get?

If the controller never ran, I check route matching and middleware. If its response is right but the final headers are wrong, I check what happens on the way out. If a dependency can't be resolved, I look at how the application registered it.

That gives me a place to start instead of changing code in whichever file happens to be open.

Pick one route in your application and follow it through these stages. Include the response coming back out. You don't need every framework detail to make that exercise useful.

For the framework's full explanation, see Laravel's request lifecycle documentation and middleware documentation.

Jeffrey Davidson

Written by Jeffrey Davidson

Laravel developer and software architect sharing practical lessons from building and maintaining real applications.

More about Jeffrey