25

I am building a REST API with Laravel 5.

In Laravel 5, you can subclass App\Http\Requests\Request to define the validation rules that must be satisfied before a particular route will be processed. For example:

<?php

namespace App\Http\Requests;

use App\Http\Requests\Request;

class BookStoreRequest extends Request {

    public function authorize() {
        return true;
    }

    public function rules() {
        return [
            'title' => 'required',
            'author_id' => 'required'
        ];
    }
}

If a client loads the corresponding route via an AJAX request, and BookStoreRequest finds that the request doesn't satisfy the rules, it will automagically return the error(s) as a JSON object. For example:

{
  "title": [
    "The title field is required."
  ]
}

However, the Request::rules() method can only validate input—and even if the input is valid, other kinds of errors could arise after the request has already been accepted and handed off to the controller. For example, let's say that the controller needs to write the new book information to a file for some reason—but the file can't be opened:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;

use App\Http\Requests\BookCreateRequest;

class BookController extends Controller {

    public function store( BookStoreRequest $request ) {

        $file = fopen( '/path/to/some/file.txt', 'a' );

        // test to make sure we got a good file handle
        if ( false === $file ) {
            // HOW CAN I RETURN AN ERROR FROM HERE?
        }

        fwrite( $file, 'book info goes here' );
        fclose( $file );

        // inform the browser of success
        return response()->json( true );

    }

}

Obviously, I could just die(), but that's super ugly. I would prefer to return my error message in the same format as the validation errors. Like this:

{
  "myErrorKey": [
    "A filesystem error occurred on the server. Please contact your administrator."
  ]
}

I could construct my own JSON object and return that—but surely Laravel supports this natively.

What's the best / cleanest way to do this? Or is there a better way to return runtime (as opposed to validate-time) errors from a Laravel REST API?

greenie2600
  • 1,679
  • 3
  • 17
  • 24
  • Why can't you just do a `return response()->json( ['error'=>'Your custom message'] );` ? – Jilson Thomas Feb 11 '16 at 02:04
  • You can build a custom json response class – Emeka Mbah Feb 11 '16 at 02:07
  • `return response()->json()` would return it with 200 OK. I want to use an appropriate non-200 response code (e.g., 500 Internal Server Error). Yeah, I could hand-code that too—I just assumed that Laravel already provided a built-in, more structured way of doing this. Maybe that's an incorrect assumption. – greenie2600 Feb 11 '16 at 02:08
  • Consider https://github.com/dingo/api for this. – ceejayoz Feb 11 '16 at 02:20
  • @greenie2600 : Did you get the solution? – Jilson Thomas Feb 11 '16 at 04:05
  • The status code can be passed to `response()->json()` as a second parameter like this: `response()->json([ 'error' => 'Your custom message' ], 400);` or `response()->json([ 'error' => 'Your custom message' ], Symfony\Component\HttpFoundation\Response::HTTP_BAD_REQUEST);` – shaedrich Apr 12 '21 at 16:05

3 Answers3

32

You can set the status code in your json response as below:

return Response::json(['error' => 'Error msg'], 404); // Status code here

Or just by using the helper function:

return response()->json(['error' => 'Error msg'], 404); // Status code here
Jilson Thomas
  • 7,165
  • 27
  • 31
9

You can do it in many ways.

First, you can use the simple response()->json() by providing a status code:

return response()->json( /** response **/, 401 );

Or, in a more complexe way to ensure that every error is a json response, you can set up an exception handler to catch a special exception and return json.

Open App\Exceptions\Handler and do the following:

class Handler extends ExceptionHandler
{
    /**
     * A list of the exception types that should not be reported.
     *
     * @var array
     */
    protected $dontReport = [
        HttpException::class,
        HttpResponseException::class,
        ModelNotFoundException::class,
        NotFoundHttpException::class,
        // Don't report MyCustomException, it's only for returning son errors.
        MyCustomException::class
    ];

    public function render($request, Exception $e)
    {
        // This is a generic response. You can the check the logs for the exceptions
        $code = 500;
        $data = [
            "error" => "We couldn't hadle this request. Please contact support."
        ];

        if($e instanceof MyCustomException) {
            $code = $e->getStatusCode();
            $data = $e->getData();
        }

        return response()->json($data, $code);
    }
}

This will return a json for any exception thrown in the application. Now, we create MyCustomException, for example in app/Exceptions:

class MyCustomException extends Exception {

    protected $data;
    protected $code;

    public static function error($data, $code = 500)
    {
        $e = new self;
        $e->setData($data);
        $e->setStatusCode($code);

        throw $e;
    }

    public function setStatusCode($code)
    {
        $this->code = $code;
    }

    public function setData($data)
    {
        $this->data = $data;
    }


    public function getStatusCode()
    {
        return $this->code;
    }

    public function getData()
    {
        return $this->data;
    }
}

We can now just use MyCustomException or any exception extending MyCustomException to return a json error.

public function store( BookStoreRequest $request ) {

    $file = fopen( '/path/to/some/file.txt', 'a' );

    // test to make sure we got a good file handle
    if ( false === $file ) {
        MyCustomException::error(['error' => 'could not open the file, check permissions.'], 403);

    }

    fwrite( $file, 'book info goes here' );
    fclose( $file );

    // inform the browser of success
    return response()->json( true );

}

Now, not only exceptions thrown via MyCustomException will return a json error, but any other exception thrown in general.

Inserve
  • 1,796
  • 1
  • 12
  • 14
Blue Genie
  • 1,897
  • 1
  • 16
  • 19
0

A simple approach is to use the abort() method in the controller. This will return an error that will be picked up by ajax error:function(){}

Controller Example

public function boost_reputation(Request $request){
    
        $page_owner = User::where('id', $request->page_owner_id)->first();

        // user needs to login to boost reputation
        if(!Auth::user()){
            toast('Sorry, you need to login first.','info');
            abort();
        }

        // page owner cannot boost his own reputation
        if(Auth::user() == $page_owner){
            toast("Sorry, you can't boost your own reputation.",'info');
            abort();
        }
}

Ajax Example

$('.reputation-btn').on('click',function(event){

   var btn = this;
   var route = "{{ route('boost_reputation') }}";
   var csrf_token = '{{ csrf_token() }}';
   var id = '{{ $page_owner->id }}';

   $.ajax({
     method: 'POST',
     url: route,
     data: {page_owner_id:id, _token:csrf_token},

     success:function(data) {
        ...your success code
     },
     error: function () {
        ...your error code
     }

   });
});

More info: https://laravel.com/docs/7.x/errors

Gass
  • 7,536
  • 3
  • 37
  • 41