Laravel API 404 Response: Return JSON Instead of Webpage Error

If you're building a Laravel project with both Web and API sides, you need to customize error messages for each of them separately. In web-view there should be error pages, and API exceptions should return JSON with status codes. How to handle it? I will show you an example with case of Model Not Found 404.

Imagine this scenario - a web page user profile, and API-based similar request.

So, we have app/Http/Controllers/UserController.php:

public function show(User $user)
{
    return view('users.show', compact('user'));
}

And then we have API - in app/Http/Controllers/API/V1/UserController.php:

public function show(User $user)
{
    return $user;
}

The second example will just return JSON'ed Collection, without any web template. And that's fine.

The problem starts when you try to load that API call and the user is not found. Since we're using Route Model Binding here and passing (User $user) directly, under the hood it's launching User::findOrFail($id) method and throws exception of ModelNotFoundException and shows 404 page.

In API side, you also get an error page instead of JSON error:

Screenshot from a Postman request

So, our goal is to stay as it is for the web version, but override the API error handling to return a proper JSON.

To do that, we need to add this logic to the app/Exceptions/Handler.php class:

use Illuminate\Database\Eloquent\ModelNotFoundException;

// ...

public function render($request, Exception $exception)
{
    if ($exception instanceof ModelNotFoundException && $request->wantsJson()) {
        return response()->json(['message' => 'Not Found!'], 404);
    }

    return parent::render($request, $exception);
}

And that's it, you will receive a well-formed response in Postman:

And it won't change the default behavior for web-based requests, it will still show good old 404 page.

For more tips how to handle API errors and exceptions, see my other in-depth article.

avatar

In laravel 10+ using Throwable and $exception => $e may be good

public function render($request, \Exception|Throwable $e) { if ($e instanceof ModelNotFoundException && $request->wantsJson()) { return response()->json(['message' => 'Not Found!'], 404); }

    return parent::render($request, $e);
}

But anyway I am not sure is this still working or my project is messed up?

avatar

well I got the solution

In the doc

public function register(): void
{
    $this->renderable(function (NotFoundHttpException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json([
                'message' => 'Record not found.'
            ], 404);
        }
    });
}

just update the register. Don't forget to add

use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
avatar

sorry for the bad styling on question. Forgot to add apostrophe

avatar

Hi, yes, the solution from the docs seems like the right one. Thanks for the comment!

Like our articles?

Become a Premium Member for $129/year or $29/month
What else you will get:
  • 58 courses (1056 lessons, total 44 h 09 min)
  • 78 long-form tutorials (one new every week)
  • access to project repositories
  • access to private Discord

Recent Premium Tutorials