Laravel API, how to properly handle errors

Laravel is already able to manage json responses by default.

Withouth customizing the render method in app\Handler.php you can simply throw a Symfony\Component\HttpKernel\Exception\HttpException, the default handler will recognize if the request header contains Accept: application/json and will print a json error message accordingly.

If debug mode is enabled it will output the stacktrace in json format too.

Here is a quick example:

<?php

...

use Symfony\Component\HttpKernel\Exception\HttpException;

class ApiController
{
    public function myAction(Request $request)
    {
        try {
            // My code...
        } catch (\Exception $e) {
            throw new HttpException(500, $e->getMessage());
        }

        return $myObject;
    }
}

Here is laravel response with debug off

{
    "message": "My custom error"
}

And here is the response with debug on

{
    "message": "My custom error",
    "exception": "Symfony\\Component\\HttpKernel\\Exception\\HttpException",
    "file": "D:\\www\\myproject\\app\\Http\\Controllers\\ApiController.php",
    "line": 24,
    "trace": [
        {
            "file": "D:\\www\\myproject\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\ControllerDispatcher.php",
            "line": 48,
            "function": "myAction",
            "class": "App\\Http\\Controllers\\ApiController",
            "type": "->"
        },
        {
            "file": "D:\\www\\myproject\\vendor\\laravel\\framework\\src\\Illuminate\\Routing\\Route.php",
            "line": 212,
            "function": "dispatch",
            "class": "Illuminate\\Routing\\ControllerDispatcher",
            "type": "->"
        },

        ...
    ]
}

Using HttpException the call will return the http status code of your choice (in this case internal server error 500)


Try this, i have used it in my project (app/Exceptions/Handler.php)

public function render($request, Exception $exception)
{
    if ($request->wantsJson()) {   //add Accept: application/json in request
        return $this->handleApiException($request, $exception);
    } else {
        $retval = parent::render($request, $exception);
    }

    return $retval;
}

Now Handle Api exception

private function handleApiException($request, Exception $exception)
{
    $exception = $this->prepareException($exception);

    if ($exception instanceof \Illuminate\Http\Exception\HttpResponseException) {
        $exception = $exception->getResponse();
    }

    if ($exception instanceof \Illuminate\Auth\AuthenticationException) {
        $exception = $this->unauthenticated($request, $exception);
    }

    if ($exception instanceof \Illuminate\Validation\ValidationException) {
        $exception = $this->convertValidationExceptionToResponse($exception, $request);
    }

    return $this->customApiResponse($exception);
}

After that custom Api handler response

private function customApiResponse($exception)
{
    if (method_exists($exception, 'getStatusCode')) {
        $statusCode = $exception->getStatusCode();
    } else {
        $statusCode = 500;
    }

    $response = [];

    switch ($statusCode) {
        case 401:
            $response['message'] = 'Unauthorized';
            break;
        case 403:
            $response['message'] = 'Forbidden';
            break;
        case 404:
            $response['message'] = 'Not Found';
            break;
        case 405:
            $response['message'] = 'Method Not Allowed';
            break;
        case 422:
            $response['message'] = $exception->original['message'];
            $response['errors'] = $exception->original['errors'];
            break;
        default:
            $response['message'] = ($statusCode == 500) ? 'Whoops, looks like something went wrong' : $exception->getMessage();
            break;
    }

    if (config('app.debug')) {
        $response['trace'] = $exception->getTrace();
        $response['code'] = $exception->getCode();
    }

    $response['status'] = $statusCode;

    return response()->json($response, $statusCode);
}

Always add Accept: application/json in your api or json request.