skip to content

Middleware Layers

Laravel's HTTP middleware: registering and ordering it in bootstrap/app.php, writing handle and terminate hooks, the default stack, and CORS. Interviewers ask what runs when, and why.

on this pageshow

explore

questions

21

In Laravel, what does php artisan make:middleware generate, and how does the generated handle() method let a request through or stop it?

level: juniorimportance: must knowfreq 74%

answer

  1. lands in app/Http/Middleware
  2. handle(Request $request, Closure $next): Response
  3. return $next($request) to continue
  4. return a redirect or response to stop
  5. --test, --pest or --phpunit adds a test

basics

~10 s

make:middleware creates a class in app/Http/Middleware with handle(Request $request, Closure $next): Response. Returning $next($request) passes the request inward; returning your own response, such as a redirect, stops it before the controller runs.

solid answer

~40 s

`php artisan make:middleware EnsureRestaurantIsOpen` writes `app/Http/Middleware/EnsureRestaurantIsOpen.php` from the framework's middleware stub: a plain class with one method, `handle(Request $request, Closure $next): Response`, whose body is `return $next($request);`. `$next` is the rest of the pipeline; calling it with the request lets inner middleware and the controller run and gives you back their response, which you must return. To stop the request, you return a response of your own instead, for example `redirect()->route('menu')` or `response()->json([...], 503)`, and the controller never runs. The return type is Symfony's `Response`, which every Laravel response extends. The command also accepts `--test`, `--pest` or `--phpunit` to generate a matching test, and the class still has to be registered before any request reaches it.

code

bash · 1 line
bash
php artisan make:middleware EnsureRestaurantIsOpen --pest

go deeper

for a junior

Recall the generated signature, handle(Request $request, Closure $next): Response, and the two outcomes: return $next($request) or return your own response.

for a middle

Explain what $next represents, why the return type matters, and how redirect, JSON and abort() differ as ways to stop a request.

for a senior

Keep middleware small and single-purpose, test the reject and pass paths, and check that each early response suits both browser and API clients.

for a principal

Decide which rules belong in middleware versus policies or form requests, so request filtering stays predictable across teams.

## What make:middleware produces A **middleware** in Laravel is a class that sits between the incoming HTTP request and the code that handles it. The generator creates one for you: ```bash php artisan make:middleware EnsureRestaurantIsOpen ``` The command, `Illuminate\Routing\Console\MiddlewareMakeCommand`, writes the file into the `App\Http\Middleware` namespace, which is `app/Http/Middleware/` in a default application. The body comes from the framework's `middleware.stub`: ```php <?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; class EnsureRestaurantIsOpen { public function handle(Request $request, Closure $next): Response { return $next($request); } } ``` A few facts about the generator itself: - It takes a single `name` argument; nested names such as `Orders/EnsureCartIsNotEmpty` create a sub-namespace. - It accepts `--test`, `--pest` and `--phpunit` to create a matching test class alongside. - After `php artisan stub:publish`, a customised `stubs/middleware.stub` in the project root is used instead of the framework's copy. - The class is **not registered** anywhere. Until it is attached to a route, a group or the global stack, it never runs. ## The two arguments of handle() - `$request` is the current `Illuminate\Http\Request`: headers, input, the authenticated user, the matched route (for route middleware). - `$next` is a `Closure` that represents **everything inside this middleware**: the remaining middleware and, finally, the controller. Calling `$next($request)` runs all of it and returns the resulting response. The return type `Symfony\Component\HttpFoundation\Response` covers every response Laravel builds: `Illuminate\Http\Response`, `JsonResponse`, `RedirectResponse`, streamed and file responses all extend it. ## Letting the request through The stub's single line is the pass-through case. When a middleware has nothing to object to, it returns whatever `$next($request)` returns. Forgetting the `return` is a classic bug: `handle()` then returns `null`, and with the `: Response` return type PHP throws a `TypeError` instead of sending the page. ## Stopping the request To stop a request, the middleware simply **does not call `$next`** and returns a response of its own: ```php public function handle(Request $request, Closure $next): Response { if (! $this->kitchen->isOpen()) { return redirect()->route('menu')->with('status', 'The kitchen is closed.'); } return $next($request); } ``` Common ways to build that response: | Response | Typical use in a food-ordering app | |---|---| | `redirect()->route('menu')` | Browser users sent back to the menu page | | `response()->json(['message' => 'Kitchen closed'], 503)` | Mobile or SPA clients calling the API | | `response()->view('closed', status: 503)` | A friendly HTML page | | `abort(403)` | Throws instead of returning; the exception handler renders the response | Whatever is returned travels back out through the middleware that wrapped this one, exactly like a controller's response would. ## What the generated class does not decide The class file only defines behaviour. Three decisions live elsewhere: 1. **Where it runs**: registration in `bootstrap/app.php` or on routes. 2. **Which parameters it receives**: extra arguments after `$next` come from the route string, for example `role:admin`. 3. **Which services it uses**: dependencies are type-hinted in the constructor, because the container builds the middleware. ## Reading the generated docblock The stub annotates `$next` as `Closure(Request): (Response)`. PHP itself cannot express a closure's signature in a type declaration, so this docblock is for static analysers and IDEs: it tells them that `$next` takes the request and returns a response, which lets them flag a `return $next;` (returning the closure itself) or a missing return. Keep it when editing the class. ## Common mistakes - Changing `$request` after calling `$next`, expecting the controller to see the change; by then the controller has already run. - Returning a string or an array from `handle()`; unlike a controller action, the declared `: Response` type rejects it. - Doing the rejection check **after** `$next`, when the controller has already performed the order it was supposed to block. ## Checklist for a new middleware - Name it after the rule it enforces (`EnsureRestaurantIsOpen`), not the mechanism. - Keep the `: Response` return type and always return something. - Return early for the rejection path; keep the happy path as `return $next($request);`. - Write the test with `--test` or `--pest` while the behaviour is fresh.

  • What happens if a Laravel middleware calls $next($request) but forgets to return its result?
    `handle()` returns `null`. Because the generated method declares `: Response`, PHP throws a `TypeError` at that point, the exception handler renders a 500, and the controller's response is lost even though the controller ran. Always `return $next($request);`.
  • Does a class created by make:middleware run as soon as it exists in app/Http/Middleware?
    No. The generator only writes the file. Laravel does not scan that directory; the class runs only after it is attached to a route or group, or added to the global stack in `bootstrap/app.php`.

saying these in an interview costs you the question

  • Expects the new middleware to run automatically once the file exists
  • Calls $next($request) and forgets to return the response
  • Thinks returning false from handle() rejects the request
  • Believes the controller still runs after an early redirect
  • Says make:middleware also edits app/Http/Kernel.php
open as a page

In a fresh Laravel 13 app with no config/cors.php, what answers CORS requests, and how do you change its settings?

level: juniorimportance: must knowfreq 55%

basics

~20 s

Laravel's HandleCors middleware answers CORS requests: it sits in the default global stack and reads the cors config. A new app runs on the framework's built-in defaults until you run php artisan config:publish cors and edit config/cors.php.

open as a page

In Laravel 13, which middleware does the web group run that the api group does not, and why does an API route have no session?

level: juniorimportance: must knowfreq 64%

basics

~10 s

The web group adds EncryptCookies, AddQueuedCookiesToResponse, StartSession, ShareErrorsFromSession and PreventRequestForgery before SubstituteBindings; the api group has only SubstituteBindings. API routes therefore never start a session, decrypt cookies or check CSRF tokens.

open as a page

In Laravel 13, where do you register a custom middleware to run on every request, and how does that differ from attaching it to routes?

level: juniorimportance: must knowfreq 78%

basics

~10 s

Global middleware is registered in bootstrap/app.php inside withMiddleware(), using $middleware->append() or prepend(); it wraps every request before routing. Route middleware is attached with ->middleware() on a route or group, by class name or alias.

open as a page

In a Laravel food-ordering app, how would you write a middleware that times each request, adds the duration to the response, and logs slow ones?

level: middleimportance: must knowfreq 58%

basics

~10 s

Record hrtime(true) before calling $next, store $response = $next($request), compute the elapsed milliseconds, set a header on $response, call Log::warning() when it exceeds a threshold, and return the response.

open as a page

In Laravel's config/cors.php, what does the paths key decide, and why can a correctly listed origin still get no CORS headers?

level: middleimportance: must knowfreq 48%

basics

~20 s

The paths key decides which requests HandleCors handles at all, and it is checked first. A request whose path matches no entry gets no Access-Control headers whatever allowed_origins says, so the browser blocks a cross-origin call to it.

open as a page

In Laravel, what do the global TrimStrings and ConvertEmptyStringsToNull middleware do to request input, and how do you exclude a field or request?

level: middleimportance: must knowfreq 56%

basics

~10 s

TrimStrings strips leading and trailing whitespace from every string input except password fields; ConvertEmptyStringsToNull then turns '' into null. Exclude keys with $middleware->trimStrings(except: [...]), and skip whole requests with closures passed to either configurator.

open as a page

In Laravel, how do you register a middleware alias such as role and pass it parameters like role:admin,agent from a route?

level: middleimportance: must knowfreq 62%

basics

~10 s

Register the alias in bootstrap/app.php with $middleware->alias(['role' => EnsureUserHasRole::class]), then write ->middleware('role:admin,agent') on the route. Laravel splits after the colon on commas and passes each value as a string argument after $next.

open as a page

A Laravel voting app behind a load balancer logs the balancer's address as every voter's IP and builds http:// links; how do you fix it with trustProxies()?

level: seniorimportance: must knowfreq 50%

basics

~10 s

Laravel trusts no proxy by default, so $request->ip() and the scheme come from the balancer's connection. Configure $middleware->trustProxies(at: [...]) in bootstrap/app.php with the balancer's addresses or CIDR range so TrustProxies honours its X-Forwarded-* headers.

open as a page

In a Laravel middleware, what is the difference between returning a response early and calling abort(403), for the middleware wrapped around it?

level: middleimportance: should knowfreq 38%

basics

~20 s

Both stop the controller. A returned response travels outward unchanged. abort(403) throws an HttpException; Laravel's routing pipeline catches it at that layer, passes it to the exception handler to render, and outer middleware receive the rendered error response.

open as a page

In Laravel, how do you give a middleware access to a service such as a kitchen-status checker, and why doesn't type-hinting it in handle() work?

level: middleimportance: should knowfreq 42%

basics

~20 s

Type-hint the service in the middleware's constructor; Laravel resolves middleware through the service container, which auto-wires constructor dependencies. handle() is invoked directly with the request, $next and any route parameters, so it gets no method injection.

open as a page

When a request comes from an origin missing from Laravel's allowed_origins, does HandleCors stop it before the controller runs?

level: middleimportance: should knowfreq 38%

basics

~20 s

No. For an actual request HandleCors always passes it on, so routing, middleware and the controller run. HandleCors only decides which Access-Control headers go on the response, and the browser then withholds that response from the calling page.

open as a page

In a Laravel app, why does $request->cookie() return null for a cookie set by front-end JavaScript, and how do you fix it?

level: middleimportance: should knowfreq 40%

basics

~20 s

EncryptCookies in the web group decrypts every incoming cookie and sets any value that fails decryption to null, so a plain cookie written by JavaScript reads as null. List it in $middleware->encryptCookies(except: ['voter_theme']) in bootstrap/app.php.

open as a page

In Laravel's bootstrap/app.php, how do you add middleware to the web or api group or define a named group, and what does group() do differently?

level: middleimportance: should knowfreq 52%

basics

~10 s

Inside withMiddleware(), $middleware->web(append: [...]) or api(prepend: [...]) extends the built-in groups, and appendToGroup() or prependToGroup() extends or creates any named group. group('name', [...]) sets the entire list, so group('web', ...) replaces the framework's defaults.

open as a page

In Laravel, how do you exempt one route from a middleware applied to its group, and why can withoutMiddleware() not remove a global middleware?

level: middleimportance: should knowfreq 44%

basics

~10 s

Chain ->withoutMiddleware(EnsureAgentIsVerified::class) on the route, or wrap routes in Route::withoutMiddleware([...])->group(). The router drops excluded classes when it resolves route middleware; global middleware runs in the kernel before routing, so it is never filtered.

open as a page

In a Laravel food-ordering app, why does a middleware's terminate() method not see a start time stored in a property during handle(), and how do you fix it?

level: seniorimportance: should knowfreq 36%

basics

~20 s

The kernel resolves a new middleware instance from the container before calling terminate(), so properties set in handle() are gone. Bind the class with $this->app->singleton() in AppServiceProvider::register(), or scoped() under Octane, so both calls share one instance.

open as a page

Your Laravel API must accept cookie-authenticated calls from a marketing site on another origin — what must change in config/cors.php, and why?

level: seniorimportance: should knowfreq 42%

basics

~10 s

Set supports_credentials to true so HandleCors sends Access-Control-Allow-Credentials: true. Replace '*' in allowed_origins with the marketing site's exact origin, and list every credentialed path in paths, including sanctum/csrf-cookie and login.

open as a page

Your Laravel API sends CORS headers on normal responses, yet the browser reports CORS errors only on some failed requests — what causes that?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Those failing responses never pass through HandleCors: PHP fatal errors rendered at shutdown, rejections by the web server or proxy before PHP runs, and failures in global middleware ahead of HandleCors. Exceptions the pipeline handles still get headers.

open as a page

In Laravel 13's bootstrap/app.php, how do you remove or replace a default middleware, and what can go wrong when you redefine the global stack with use()?

level: seniorimportance: should knowfreq 30%

basics

~10 s

Use $middleware->remove(Class::class) or replace(Old::class, New::class) for the global stack, and web(remove: [...], replace: [...]) or api(...) for the groups. use([...]) replaces the entire global list, so anything you omit, including trustHosts(), silently stops running.

open as a page

On a Laravel travel-agency site, a SetLocale middleware appended to the web group runs after SubstituteBindings, so localized destination slugs fail to bind; how do you fix the order?

level: seniorimportance: should knowfreq 34%

basics

~10 s

Add SetLocale to the middleware priority list ahead of SubstituteBindings with $middleware->prependToPriorityList(before: SubstituteBindings::class, prepend: SetLocale::class). The router then moves it above binding while keeping it after StartSession; avoid priority([...]), which replaces the default list.

open as a page

In Laravel, why does an oversized upload fail with a 413 PostTooLargeException before any validation rule runs?

level: middleimportance: nice to knowfreq 24%

basics

~10 s

The global ValidatePostSize middleware compares the request's Content-Length with PHP's post_max_size and throws PostTooLargeException, an HTTP 413, when it is larger. It runs before routing, so validation rules such as max never execute.

open as a page