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.

Like our articles?
Check out our Laravel online courses!

10 COMMENTS

    • Yes, that’s the most straightforward way, but you would be surprised how many developers forget it, or don’t even know about it.

    • I agree that you can use application/json, but if you are testing using a browser, adding headers becomes cumbersome. APIs should have a default return type of json.

      For consistencies sake, if a successful API request returns json without the Accept header, it should also return json on failure without the Accept header.

  1. How can i catch multiple excepctions, web exceptions return 404 as default. Bu how can i handle around 15 json exceptions? I have an ecommerce site with vue, I need to handle around 15 – 20 exceptions from different packages. I dont want to repeat if else if. There should be a better way

    • I don’t think there’s a better way, in Exception Handler’s render() method you just list all possible exception classes and how you want to get them returned. if-else, switch-case or whatever you prefer.

  2. You should not use isJson() but wantsJson(). The first one is about the request, the second one is about the desired response.

  3. I done this using middleware before, so if the request hit api/* (browser/postman) it will go through middleware that add Accept: application/json header automatically

  4. I added the exact logic to the render method of the app\Exceptions\Handler.php file in a Laravel 5.7 app but I am still getting the html response when an api call is made. Is there something I am suppose to do first?

  5. I’m using your technique with Laravel 5.7 trying to capture PostTooLargeException, but the response is prefixed with (tags removed):
    br
    b Warning /b: POST Content-Length of 28408876 bytes exceeds the limit of 20971520 bytes in
    b Unknown /b on line b 0 /b br

    after this I got my custom message:
    {“message”

    How can I suppress the textual warning?, or at least where I should look into?

LEAVE A REPLY

Please enter your comment!
Please enter your name here