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 401 and 403,
  • 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 catch blocks 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