Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to produce API error responses in Laravel 5.4?

Whenever I make a call to /api/v1/posts/1, the call is forwarded to the show method

public function show(Post $post) {
    return $post;
}

in PostController.php resourceful controller. If the post does exist, the server returns a JSON response. However, if the post does not exist, the server returns plain HTML, despite the request clearly expecting JSON in return. Here's a demonstration with Postman.

enter image description here

The problem is that an API is supposed to return application/json, not text/html. So, here are my questions:

1. Does Laravel have built-in support for automatically returning JSON if exceptions occur when we use implicit route model binding (like in show method above, when we have 404)?

2. If it does, how do I enable it? (by default, I get plain HTML, not JSON)

If it doesn't what's the alternative to replicating the following across every single API controller

public function show($id) {
    $post = Post::find($id); // findOrFail() won't return JSON, only plain HTML
    if (!$post)
        return response()->json([ ... ], 404);
    return $post;
}

3. Is there a generic approach to use in app\Exceptions\Handler?

4. What does a standard error/exception response contain? I googled this but found many custom variations.

5. And why isn't JSON response still built into implicit route model binding? Why not simplify devs life and handle this lower-level fuss automatically?

EDIT

I am left with a conundrum after the folks at Laravel IRC advised me to leave the error responses alone, arguing that standard HTTP exceptions are rendered as HTML by default, and the system that consumes the API should handle 404s without looking at the body. I hope more people will join the discussion, and I wonder how you guys will respond.

like image 806
Alex Avatar asked Apr 06 '17 04:04

Alex


1 Answers

I use this code in app/Exceptions/Handler.php, probably you will need making some changes

public function render($request, Exception $exception)
{
    $exception = $this->prepareException($exception);

    if ($exception instanceof \Illuminate\Http\Exception\HttpResponseException) {
        return $exception->getResponse();
    }
    if ($exception instanceof \Illuminate\Auth\AuthenticationException) {
        return $this->unauthenticated($request, $exception);
    }
    if ($exception instanceof \Illuminate\Validation\ValidationException) {
        return $this->convertValidationExceptionToResponse($exception, $request);
    }

    $response = [];

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

    switch ($statusCode) {
        case 404:
            $response['error'] = 'Not Found';
            break;

        case 403:
            $response['error'] = 'Forbidden';
            break;

        default:
            $response['error'] = $exception->getMessage();
            break;
    }

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

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

Additionally, if you will use formRequest validations, you need override the method response, or you will be redirected and it may cause some errors.

use Illuminate\Http\JsonResponse;

...

public function response(array $errors)
{
    // This will always return JSON object error messages
    return new JsonResponse($errors, 422);
}
like image 173
Juliano Petronetto Avatar answered Oct 06 '22 17:10

Juliano Petronetto