Laravel & Lumen: Return Correct Error Responses for Requests

I’ve seen a few pieces of API code dotted around the web recently where people have written simple JSON APIs but their error handling still returns HTML instead of JSON for 404 errors and the likes. This is pretty simple to solve with Laravel or Lumen and easily testable too. The cleanest way I’ve seen of handling this was written by Paul Redmond in his book ‘Writing APIs with Lumen’ which you can purchase on Leanpub.

~

I’ve seen a few pieces of API code dotted around the web recently where people have written simple JSON APIs but their error handling still returns HTML instead of JSON for 404 errors and the likes. This is pretty simple to solve with Laravel or Lumen and easily testable too. The cleanest way I’ve seen of handling this was written by Paul Redmond in his book ‘Writing APIs with Lumen’ which you can purchase on Leanpub.

In your error handler, typically located at app/Exceptions/Handler.php, you’ll need to add a check for wantsJson() on the Request object, like this:

<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Validation\ValidationException;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Laravel\Lumen\Exceptions\Handler as ExceptionHandler;
use Symfony\Component\HttpFoundation\Response;

class Handler extends ExceptionHandler
{
    /**
     * A list of the exception types that should not be reported.
     *
     * @var array
     */
    protected $dontReport = [
        AuthorizationException::class,
        HttpException::class,
        ModelNotFoundException::class,
        ValidationException::class,
    ];

    /**
     * Report or log an exception.
     *
     * This is a great spot to send exceptions to Sentry, Bugsnag, etc.
     *
     * @param  \Exception  $e
     * @return void
     */
    public function report(Exception $e)
    {
        parent::report($e);
    }

    /**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Exception  $e
     * @return \Illuminate\Http\Response
     */
    public function render($request, Exception $e)
    {
        if ($request->wantsJson()) {
            $response = [
                'message' => (string) $e->getMessage(),
                'status' => 400
            ];

            if ($e instanceof HttpException) {
                $response['message'] = Response::$statusTexts[$e->getStatusCode()];
                $response['status'] = $e->getStatusCode();
            } else if ($e instanceof ModelNotFoundException) {
                $response['message'] = Response::$statusTexts[Response::HTTP_NOT_FOUND];
                $response['status'] = Response::HTTP_NOT_FOUND;
            }

            if ($this->isDebugMode()) {
                $response['debug'] = [
                    'exception' => get_class($e),
                    'trace' => $e->getTrace()
                ];
            }

            return response()->json(['error' => $response], $response['status']);
        }

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

    /**
     * Determine if the application is in debug mode.
     *
     * @return Boolean
     */
    public function isDebugMode()
    {
        return (boolean) env('APP_DEBUG');
    }
}

Pretty simple and effective, you can handle more cases within your handler as well, but that’s enough to illustrate the idea. You can test the above is working by using something similar to this test:

class ExampleTest extends TestCase
{
    /**
     * A basic test example.
     *
     * @return void
     */
    public function testExample()
    {

        $this->json("get", "/page-that-does-not-exist")
             ->seeJsonStructure([
                 "error"
             ]);

    }
}

A quick and simple tip, but one that seems to be overlooked often.