Alex
Alex

Reputation: 3855

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.

Upvotes: 6

Views: 16569

Answers (4)

Juliano Petronetto
Juliano Petronetto

Reputation: 1167

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);
}

Upvotes: 6

francis ngangue
francis ngangue

Reputation: 51

You just use use Illuminate\Support\Facades\Response;. then, make the return as am:

public function index(){
    $analysis = Analysis::all();
    if(empty($analysis)) return Response::json(['error'=>'Empty data'], 200);
    return Response::json($analysis, 200, [], JSON_NUMERIC_CHECK);
}

And now you will have a JSON return....

Upvotes: -3

Sayantan Das
Sayantan Das

Reputation: 1641

The way we have handled it by creating a base controller which takes care of the returning response part. Looks something like this,

class BaseApiController extends Controller
{

    private $responseStatus = [
        'status' => [
            'isSuccess' => true,
            'statusCode' => 200,
            'message' => '',
        ]
    ];

    // Setter method for the response status
    public function setResponseStatus(bool $isSuccess = true, int $statusCode = 200, string $message = '')
    {
        $this->responseStatus['status']['isSuccess'] = $isSuccess;
        $this->responseStatus['status']['statusCode'] = $statusCode;
        $this->responseStatus['status']['message'] = $message;
    }

    // Returns the response with only status key
    public function sendResponseStatus($isSuccess = true, $statusCode = 200, $message = '')
    {

        $this->responseStatus['status']['isSuccess'] = $isSuccess;
        $this->responseStatus['status']['statusCode'] = $statusCode;
        $this->responseStatus['status']['message'] = $message;

        $json = $this->responseStatus;

        return response()->json($json, $this->responseStatus['status']['statusCode']);

    }

    // If you have additional data to send in the response
    public function sendResponseData($data)
    {

        $tdata = $this->dataTransformer($data);

        if(!empty($this->meta)) $tdata['meta'] = $this->meta;

        $json = [
            'status' => $this->responseStatus['status'],
            'data' => $tdata,
        ];


        return response()->json($json, $this->responseStatus['status']['statusCode']);

    }
}

Now you need to extend this in your controller

class PostController extends BaseApiController {

    public function show($id) {
        $post = \App\Post::find($id);
        if(!$post) {
            return $this->sendResponseStatus(false, 404, 'Post not found');
        }

        $this->setResponseStatus(true, 200, 'Your post');
        return $this->sendResponseData(['post' => $post]);
    }
}

You would get response like this

{
  "status": {
    "isSuccess": false,
    "statusCode": 404,
    "message": "Post not found"
  }
}

{
  "status": {
    "isSuccess": true,
    "statusCode": 200,
    "message": "Your post"
  },
  "data": {
     "post": {
         //Your post data
      }
  }
}

Upvotes: -1

Jared Rolt
Jared Rolt

Reputation: 591

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

You can check if json is expected in the generic exception handler.

// app/Exceptions/Handler.php
public function render($request, Exception $exception) {
    if ($request->expectsJson()) {
        return response()->json(["message" => $exception->getMessage()]);
    }
    return parent::render($request, $exception);
}

Upvotes: 4

Related Questions