Custom API error messages with Laravel 5+

Posted on: 20/07/2018 in Programming. Last Updated on: 02/11/2018


So first things first, why would you want to create your own custom error messages for your API? I'll outline some use cases:

  • You don't want to display the standard 404 page not found text - "Sorry, the page you are looking for could not be found."
    • This returns HTML instead of JSON, not ideal when using an API
  • You want to customise the error messages based on the exceptions that Laravel can throw:
    • ModelNotFoundException
      • Happens when you request a resource by ID which doesn't exist. E.g. /api/cars/44556
    • NotFoundHttpException
      • Happens when an incorrect endpoint / url is requested
    • MethodNotAllowedHttpException
      • Happens when someone requests a restricted endpoint or tried to issue an GET request on a PATCH etc

So to give a more in-depth example, lets take the ModelNotFoundException example. If someone were to issue a request to your API to fetch a resource which doesn't exist. By defaut Laravel would respond with the following:

{
"message": "No query results for model [App\\Cars]."
}

There is one very big issue with this... You have exposed the name of the model and also given away the name of the database table. This might be ok in a development environment, but in an production env, this message needs to go.

How to create custom API error messages

So this is actually not that difficult, however a lot of examples on the Internet miss out simple steps which are easy to forget and can leave you scratching your head!

The ways i've found of doing this live in two places: /routes/api.php and /app/Exceptions/Handler.php. Let's start with /routes/api.php.

At the top of this file, but below the initial comments, place the following:

// Fallback route incase anything goes wrong
Route::fallback(function(){
    return response()->json(['error' => 'Resource not found.'], 404);
})->name('fallback');

Version 5.5 introduced a new route method called fallback. This can be used to catch or "fallback" on this method when an non-existant endoint / URL has been requested.

For example, if a user requested the following endpoint https://api.yourapplication.com/api/non-existant-link by default Laravel would response with a HTML based 404 error page and since we are consuming an API, you'd expect the 404 response to come back as JSON. Well fear not, using the fallback method, you can catch all bad endpoints and return the response as JSON and the HTTP error code of your choice, nice!

You can also give this route a name if you want to reference it when redirecting users within the API.

The next step is to handle any Exceptions that Laravel will throw when something goes wrong. Head over to /app/Exceptions/Handler.php and scroll down to the render() method.

Update the function to have the following code, but don't forget to add the following above the class declaration!

use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Illuminate\Database\Eloquent\ModelNotFoundException;

/**
 * Render an exception into an HTTP response.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Exception  $exception
 * @return \Illuminate\Http\Response
 */
public function render($request, Exception $exception)
{

    // This will replace our 404 response with a JSON response.
    if ($exception instanceof ModelNotFoundException && $request->wantsJson()) {
        return response()->json([
            'error' => 'Resource item not found.'
        ], 404);
    }

    if ($exception instanceof NotFoundHttpException && $request->wantsJson()) {
        return response()->json([
            'error' => 'Resource not found.'
        ], 404);
    }

    if ($exception instanceof MethodNotAllowedHttpException && $request->wantsJson()) {
        return response()->json([
            'error' => 'Method not allowed.'
        ], 405);
    }

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

 

Let's break down what we've added. First of all were creating an IF Statement which checks to see if the Exception thrown matches the one we want to catch AND if the request expects a JSON response. This allows us to filter out anything which might not be an API call to our service, I.e someone lands on a non-existant front-facing URL of your application, Laravel would handle this with its standard HTML 404 page.

However, if the exception matches and its a request to one of your API endpoints, we can instruct Laravel to return a JSON object, where we can sepcify an error message, or anything you like really and also the correct HTTP response code.

The 3 Exceptions above are ones that I've exprienced regularly when developing my own API, you can add as many as you like, just make sure to use an instance of the Exception above the class declaration.

**Update** 

At the time of writing, I was using Laravel 5.5. If you're using a later version, you might find that the above method doesn't work anymore. I solved this issue by removing the "&& $request->wantsJson()" part of each IF statement and it works a charm!