lanin/laravel-api-debugger
Inspect and debug Laravel API requests without scattering dumps: laravel-api-debugger captures headers, payloads, responses, status codes, and timing in a built-in UI, making it easy to trace failures, compare calls, and profile endpoints during development and testing.
Install (dev only). The package is a debugging aid, so keep it out of production dependencies:
composer require lanin/laravel-api-debugger --dev
If you're on a Laravel version old enough to lack package auto-discovery, register the service provider manually in config/app.php. Then publish the config so you can see the actual toggle and secret keys rather than guessing:
php artisan vendor:publish --provider="Lanin\LaravelApiDebugger\ApiDebuggerServiceProvider"
Where to look first. In order:
config/api-debugger.php — this tells you whether debugging is environment-gated, whether it requires a secret/header, and what payload shape you'll get back.src/Middleware directory — this is the whole product. Read it end-to-end before adopting; it's small enough to audit in one sitting, which matters for a package with essentially no dependents.app/Http/Kernel.php (or bootstrap/app.php on Laravel 11+) — the middleware must actually be attached to the api group or to individual routes.First use case. Register the middleware on your API group, hit an endpoint with the debug trigger (typically a query param like ?debug=1 or an X-Debug header — confirm the exact key in the config), and you should get your normal JSON back with an appended debug block containing the query log, timings, and collected logs. That single request is the whole value proposition: you see the N+1 you just wrote without leaving the HTTP client.
Opt-in per request, not per environment. The best workflow is to leave the middleware registered everywhere in local/staging but only emit data when the request explicitly asks for it. That way Postman/Insomnia collections can opt in on specific calls while normal browser and frontend traffic stays clean. Add the header to a shared environment in your API client so every team member gets it for free.
Pair it with an axios interceptor in local dev. In your SPA, attach the debug header only when NODE_ENV !== 'production', then console.group the returned debug block. This turns "why is this page slow" into a single network-tab inspection instead of a Telescope tab-switch.
if (import.meta.env.DEV) {
http.interceptors.request.use(c => {
c.headers['X-Debug'] = '1';
return c;
});
}
Use it as a query-budget assertion in tests. The most underrated pattern: call an endpoint with the debug trigger inside a feature test, read the debug payload, and assert the query count is below a threshold. This catches N+1 regressions at CI time rather than in review:
$response = $this->withHeaders(['X-Debug' => '1'])->getJson('/api/orders');
$this->assertLessThan(15, count($response->json('_debug.queries')));
Route-scoped registration for noisy endpoints. If a couple of endpoints return huge payloads or binary/streamed content, register the middleware on specific routes rather than the whole group. Middleware order matters — put it early in the group so it wraps auth and controller execution and captures everything, but be aware that middleware before it (like SubstituteBindings) won't be included in its timings.
Treat the debug payload as a spec you own. Because the payload includes bindings, request input, and logs, it doubles as a lightweight contract for what "debuggable" means in your app. If you extend it with custom collectors (most packages of this shape expose a collector/extension point), keep those additions in your own service provider so a composer update can't silently drop them.
false when APP_DEBUG=false, and add a belt-and-braces check in the middleware registration itself. Do not rely on the package's default alone — it has no meaningful adoption history to vouch for it._debug key breaks strict clients. Any consumer doing assertExactJson, JSON-schema validation, or typed DTO deserialization will choke when the extra key appears. Keep the debug trigger off in contract tests, and make sure the frontend never forwards the debug header by default in shared code.204 No Content. If a route "doesn't show debug info," check the response class before assuming the package is broken.Access-Control-Expose-Headers) are required before your browser JS can read them. This is the most common "it works in Postman but not the browser" cause.DB::listen calls means every query is recorded several times — expect a measurable overhead on query-heavy endpoints and don't benchmark with all of them on.illuminate/* constraints against your Laravel version before upgrading the framework, and be prepared to fork it — the middleware is small enough that vendoring it into app/Support is a legitimate option if you only need the query log and timing collection. If you want something maintained, compare against Debugbar's JSON mode or a hand-rolled DB::listen + response macro before committing.Accept: application/json (so Laravel returns a JsonResponse), confirm the trigger header/param name matches the config exactly, and confirm the response isn't cached by a route cache or HTTP cache layer.How can I help you explore Laravel packages today?