Originally published on laravel.learnio.dev — part of the Laravel best practices course. View this lesson on the original site.
Keep failures predictable for users and clients
Good error handling means the same kind of failure gets the same response shape every time.
What Laravel does for you by default
- missing model in binding usually returns
404, - validation failures usually return
422, - auth and authorisation failures map to
401and403, - unexpected failures become
500.
This gives you a reliable baseline before custom rules.
Use abort() for deliberate HTTP errors
abort(404);
Use this for intentional HTTP outcomes, not as a substitute for business logic.
Render custom JSON for API errors
In bootstrap/app.php, you can customise rendering:
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->render(function (NotFoundHttpException $e, Request $request) {
if ($request->expectsJson()) {
return response()->json(['message' => 'Record not found.'], 404);
}
});
})
Keep browser responses user-friendly and API responses machine-friendly.
Reporting vs rendering
- reporting sends exceptions to logs or monitoring tools,
- rendering decides what HTTP response the client gets.
Do not mix these concerns in controllers.
Bad way vs better way
Bad way (swallow and return vague generic payload in every action):
try {
// action logic
} catch (\Throwable $e) {
return response()->json(['error' => 'Something went wrong'], 500);
}
Better way (let framework and central handler map failures):
// controller stays focused
$order = Order::findOrFail($id);
return response()->json(['data' => $order]);
Why better:
- error behaviour is consistent across endpoints,
- less duplicated catch boilerplate,
- easier monitoring and debugging.
Practical exception checklist
- use framework defaults where they already match intent,
- centralise JSON error rendering for API routes,
- avoid broad
catchblocks unless you can recover, - include meaningful logging context, not sensitive input.
Failure flow
flowchart LR
A[Request and controller action] --> B[Exception raised]
B --> C[Report pipeline]
B --> D[Render pipeline]
D --> E[HTML or JSON response]
Next, you will consolidate Chapter 2 with a full exercise and quiz to verify routing and controller decisions end to end.
From Laravel best practices — Routing, Controllers & Request Flow