Writing a Middleware Class

php artisan make:middleware AddServerTiming writes a class to app/Http/Middleware (add --pest or --phpunit for a test) whose handle only returns $next($request). Add use Illuminate\Support\Str; and fill it in: the before part starts a clock and adopts an incoming X-Request-Id or mints a UUID; the after part writes the standard Server-Timing header, shown in browsers' Timing tab.

The handle method of app/Http/Middleware/AddServerTiming.phpPHP
public function handle(Request $request, Closure $next): Response
{
    $start = hrtime(true);                                     // before
    $id = $request->header('X-Request-Id') ?: (string) Str::uuid();
    $request->headers->set('X-Request-Id', $id);
    $response = $next($request);                               // the rest of the app
    $ms = (hrtime(true) - $start) / 1e6;                       // after
    $response->headers->set('Server-Timing', sprintf('app;dur=%.1f', $ms));
    $response->headers->set('X-Request-Id', $id);
    return $response;
}

Registered globally (Registering Middleware), it tags even the /up health route, and a caller's own ID comes back unchanged:

Checking the headers with curlShell
curl -si -H 'X-Request-Id: order-1042' http://127.0.0.1:8304/up -o /dev/null -D -
Output
HTTP/1.1 200 OK
Host: 127.0.0.1:8304
Connection: close
X-Powered-By: PHP/8.5.4
Cache-Control: no-cache, private
Date: Wed, 23 Sep 2026 07:21:35 GMT
Content-Type: text/html; charset=utf-8
Server-Timing: app;dur=33.4
X-Request-Id: order-1042

Always return a Response, since null or a string fails the return type; put after-work after $next; and keep per-request state out of properties, because under Octane 4,043 one instance serves many requests (Octane). Constructor dependencies are injected from the service container.