How can I make Laravel return a custom error for a JSON REST API
Asked Answered
M

10

42

I'm developing some kind of RESTful API. When some error occurs, I throw an App::abort($code, $message) error.

The problem is: I want him to throw a json formed array with keys "code" and "message", each one containing the above mentioned data.

Array
(
    [code] => 401
    [message] => "Invalid User"
)

Does any one knows if it's possible, and if it is, how I do it?

Manet answered 14/3, 2014 at 19:53 Comment(0)
U
58

go to your app/start/global.php.

This will convert all errors for 401 and 404 to a custom json error instead of the Whoops stacktrace. Add this:

App::error(function(Exception $exception, $code)
{
    Log::error($exception);

    $message = $exception->getMessage();

    // switch statements provided in case you need to add
    // additional logic for specific error code.
    switch ($code) {
        case 401:
            return Response::json(array(
                    'code'      =>  401,
                    'message'   =>  $message
                ), 401);
        case 404:
            $message            = (!$message ? $message = 'the requested resource was not found' : $message);
            return Response::json(array(
                    'code'      =>  404,
                    'message'   =>  $message
                ), 404);        
    }

});

This is one of many options to handle this errors.


Making an API it is best to create your own helper like Responser::error(400, 'damn') that extends the Response class.

Somewhat like:

public static function error($code = 400, $message = null)
{
    // check if $message is object and transforms it into an array
    if (is_object($message)) { $message = $message->toArray(); }

    switch ($code) {
        default:
            $code_message = 'error_occured';
            break;
    }

    $data = array(
            'code'      => $code,
            'message'   => $code_message,
            'data'      => $message
        );

    // return an error
    return Response::json($data, $code);
}
Underbrush answered 14/3, 2014 at 23:20 Comment(2)
I really liked the second thing you said. About the API, though I didn't understand it entirely. Should I create another class, where I extends Response class and call it instead of the "right" one? It's that it?Manet
@DennisBraga that is right. It helps you keep a uniformed error response in your API.Underbrush
P
42

You can pass an array to the returned JSON response:

$returnData = array(
    'status' => 'error',
    'message' => 'An error occurred!'
);
return Response::json($returnData, 500);
Pohl answered 14/3, 2014 at 20:35 Comment(2)
Thanks! In laravel 5 it would be: return response()->json($returnData, 500)Inadvertency
could also be made more easier if you reduce the useage of the $returnData arrayNorikonorina
D
21

Here is what I use (Laravel 5.2):

According to: https://laravel.com/docs/5.2/errors , we can specify a custom render function for errors in app\Exceptions\Handler.php. All I did was to change my render function to this:

    /**
     * Render an exception into an HTTP response.
     * Updated to return json for a request that wantsJson 
     * i.e: specifies 
     *      Accept: application/json
     * in its header
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Exception  $e
     * @return \Illuminate\Http\Response
     */
    public function render($request, Exception $e)
    {
        if ($request->ajax() || $request->wantsJson()) {
            return response()->json(
                          $this->getJsonMessage($e), 
                          $this->getExceptionHTTPStatusCode($e)
                        );
        }
        return parent::render($request, $e);
    }

    protected function getJsonMessage($e){
        // You may add in the code, but it's duplication
        return [
                  'status' => 'false',
                  'message' => $e->getMessage()
               ];
    }

    protected function getExceptionHTTPStatusCode($e){
        // Not all Exceptions have a http status code
        // We will give Error 500 if none found
        return method_exists($e, 'getStatusCode') ? 
                         $e->getStatusCode() : 500;
    }

After this, all you need do is make sure all your API requests specify the Accept: application/json header. Hope this helps :)

Diecious answered 24/6, 2016 at 7:43 Comment(1)
You are the King! with only a tiny snippet i can handle ajax response. Cool. example: abort( 401, 'Not Authorized' );Aristocrat
G
21

Heres what I used in 5.6 in order to return the same type of response as the built-in validate method:

response()->json(['errors' => ['email' => ['The email is invalid.']]], 422);

Gielgud answered 26/3, 2018 at 2:40 Comment(0)
C
10

Laravel 6:

You have to set Accept:application/json header in your API request from client-side and Laravel will automatically return a JSON format error.

 {     
    "message": "Unauthenticated."
 }
Cataplasm answered 2/2, 2020 at 15:29 Comment(1)
Greate I was missed that in logout API working fine now. ThanksBlurt
T
8

According to Ibrahim's answer, not every ajax request wants JSON, Responding the "status code" and the "status" is unnecessary since they both mean the same thing. More than that, there's no need to mention in the response "status" at all, since the response code "says" that. Something like that should work perfectly:

/**
 * 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())
        return response()->json(
            ['message' => $e->getMessage()],
            method_exists($e, 'getStatusCode') ? $e->getStatusCode() : 500);

    return parent::render($request, $e);
}
Trilinear answered 6/7, 2016 at 9:43 Comment(0)
V
4

For Laravel 8

go to your \app\Exceptions\Handler.php and override invalidJson method like this:

// Add this line at the top of the class
use Illuminate\Validation\ValidationException;


/**
 * Convert a validation exception into a JSON response.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Illuminate\Validation\ValidationException  $exception
 * @return \Illuminate\Http\JsonResponse
 */
protected function invalidJson($request, ValidationException $exception)
{
    // You can return json response with your custom form
    return response()->json([
        'success' => false,
        'data' => [
            'code' => $exception->status,
            'message' => $exception->getMessage(),
            'errors' => $exception->errors()
        ]
    ], $exception->status);
}

Response Sample:

{
  "success": false,
  "data": {
    "code": 422,
    "message": "The given data was invalid.",
    "errors": {
      "password": [
        "The password field is required."
      ]
    }
  }
}

The original method was:

/**
 * Convert a validation exception into a JSON response.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Illuminate\Validation\ValidationException  $exception
 * @return \Illuminate\Http\JsonResponse
 */
protected function invalidJson($request, ValidationException $exception)
{
    return response()->json([
        'message' => $exception->getMessage(),
        'errors' => $exception->errors(),
    ], $exception->status);
}

Response Sample:

{
  "message": "The given data was invalid.",
  "errors": {
    "password": [
      "The password field is required."
    ]
  }
}

Note that unauthenticated response is in separate method, so you can override it as well

/**
 * Convert an authentication exception into a response.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Illuminate\Auth\AuthenticationException  $exception
 * @return \Symfony\Component\HttpFoundation\Response
 */
protected function unauthenticated($request, AuthenticationException $exception)
{
    return $request->expectsJson()
        // Here you can change the form of the json response
        ? response()->json(['message' => $exception->getMessage()], 401) // <-
        : redirect()->guest($exception->redirectTo() ?? route('login'));
}
Vibraphone answered 1/7, 2021 at 18:48 Comment(2)
invalidJson() don't existe in \app\Exceptions\Handler.php on laravel 8.Krilov
\app\Exceptions\Handler.php extends Illuminate\Foundation\Exceptions\Handler, invalidJson() exists in parent class, you just need to override it to change its behaviorVibraphone
A
2
$response['message'] ="The given data was invalid";
$error['country_id'] = ["The country field is required"];
$error['state_id'] = ["The state field is required"];
$error['name'] = ["The name field is required"];
$response['error'] = $error;
return response()->json($response,422);
Anesthetize answered 10/8, 2020 at 16:53 Comment(0)
M
2
public function register()
{
   
    $this->renderable(function (NotFoundHttpException $e, $request) {
        $returnData = array(
            'status' => 'error',
            'message' => 'Record not found'
        );
        return response()->json($returnData, "404");
    });
}
Meyer answered 20/4, 2022 at 8:28 Comment(1)
Remember to add use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;Meyer
D
1

In Laravel5.6, I usually specify a custom render function for errors in app\Exceptions\Handler.php. All I did was to change my render function to this:

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

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

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

        return response()->json([
            'status'      => 'failed',
            'status_code' => $response['status_code'],
            'massage'     => $response['message'],
        ], $response['status_code']);
    }

    return parent::render($request, $e);
}
Dwelt answered 24/5, 2018 at 23:57 Comment(1)
That answer looke really good, but sadly it doesn't work for me, Laravel still gives me ```status "error" message "An error occurred."```` as response :(Petuu

© 2022 - 2024 — McMap. All rights reserved.