7

I am trying to create a RESTful API using laravel, I'm trying to fetch a resource with an invalid ID, and the result is 404 since it is not found, but my problem is the response is not in JSON format, but a View 404 (by default) with HTML. Is there any way to convert the response into JSON? For this situation, I use Homestead.

I try to include a fallback route, but it does not seem to fit this case.

Route::fallback(function () {
    return response()->json(['message' => 'Not Found.'], 404);
});

I try to modify the Handler (App\Exceptions), but nothing change.

public function render($request, Exception $e)
{
    if ($e instanceof ModelNotFoundException) {
        if ($request->ajax()) {
            return response()->toJson([
                'message' => 'Not Found.',
            ], 404);
        }
    }

    return parent::render($request, $e);
}
Karl Hill
  • 12,937
  • 5
  • 58
  • 95
hmmer_Head
  • 71
  • 1
  • 3
  • Does `$e instanceof \Illuminate\Database\Eloquent\ModelNotFoundException` work? – ceejayoz May 31 '19 at 20:07
  • You'll need to send the correct content-type: `'content-type':'application/json'` – Adam Rodriguez May 31 '19 at 20:15
  • About ModelNotFoundException - No. it seems that it passed direct. I can not understand. When i use the GET method, to catch all results, it's, OK (Json response), but, in one case (One particular ID e.g), all results is a HTML response – hmmer_Head May 31 '19 at 20:57

6 Answers6

8

For Laravel 9

use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
/**
 * Register the exception handling callbacks for the application.
 *
 * @return void
 */
public function register()
{
    $this->renderable(function (NotFoundHttpException $e, $request) {
        if ($request->is('api/*')) {
            return response()->json([
                'message' => 'Record not found.'
            ], 404);
        }
    });
}
Rahat
  • 111
  • 3
  • 5
  • 2
    in App/Exceptions/Handler.php – Rahat Mar 01 '22 at 12:13
  • but If we wants to check instance of ModelNotFoundException than how can do it? & how we get model name & id if we needed for ModelNotFoundExcepton in laravel 9 – Harsh Patel Feb 18 '23 at 09:34
  • for Haanlde ModelNotFoundException in laravel 9, we can do it as like this https://stackoverflow.com/a/75492554/14344959 – Harsh Patel Feb 18 '23 at 10:06
3

if your project is only a RESTful API and no views, you could add a new middleware which add ['accept' => 'application/json'] header to all request. this will ensure that all response will return a json, instead of the views

<?php

namespace App\Http\Middleware;

use Closure;

class AddAjaxHeader
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $request->headers->add(['accept' => 'application/json']);
        return $next($request);
    }
}

and add it into Kernel.php

Joe
  • 774
  • 6
  • 23
  • In this case, i dont use only API. This project have a frontend to. But i'll try to use your exmple, and give you some feedback. thanks – hmmer_Head Jun 03 '19 at 11:08
  • I added this to my `api` middleware group and it works well for those routes, which is helpful for many cases (e.g. when returning 403 from a policy auth failure in a controller.) Obviously it won't work for the case where a completely wrong URI is used! – miken32 Dec 10 '20 at 18:59
2

You'll need to send the correct Accept header in your request: 'Accept':'application/json'.

Then Illuminate\Foundation\Exceptions\Handler will care of the formatting in the render method in your response:

return $request->expectsJson()
                    ? $this->prepareJsonResponse($request, $e)
                    : $this->prepareResponse($request, $e);
Adam Rodriguez
  • 1,850
  • 1
  • 12
  • 15
1

In Laravel 9, as per Official Documentation ModelNotFoundException is directly forwarded to NotFoundHttpException (which is a part of Symfony Component) that used by Laravel and will ultimately triggers a 404 HTTP response.

so, we need to checking Previous Exception using $e->getPrevious() just check previous exception is instanceof ModelNotFoundException or not

see below my code

// app/Exceptions/Handler.php file

$this->renderable(function (NotFoundHttpException $e, $request) {
    if ($request->is('api/*')) {
        if ($e->getPrevious() instanceof ModelNotFoundException) {
            /** @var ModelNotFoundException $modelNotFound */
            $modelNotFound = $e->getPrevious();
            if($modelNotFound->getModel() === Product::class) {
                return response()->json([
                    'message' => 'Product not found.'
                ], 404);
            }
        }

        return response()->json([
            'message' => 'not found.'
        ], 404);
    }
});

additionally, In API Response, if you are getting view as a response instead of JSON during other HTTP responses like HTTP 422 (validation error), 500 Internal server error. because $request->wantsJson() uses the Accept header sent by the client to determine if it wants a JSON response. So you need to set 'Accept' header value as "application/json" in request. see Validation Error Response

Method 1 :- set accept value as a application/json to every incoming api requests using middleware

JsonResponseApiMiddleware middleware created for set accept header to every incoming api requests

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class JsonResponseApiMiddleware
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse)  $next
     * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
     */
    public function handle(Request $request, Closure $next)
    {
        // $acceptHeader = $request->header('Accept');
        
        // set 'accept' header value as a json to all request.
        $request->headers->set('accept', 'application/json', true);

        return $next($request);
    }
}


set JsonResponseApiMiddleware middleware class to api middlewareGroups. for this open Kernel.php file at App\Http directory

// App/Http/Kernel.php file

    protected $middlewareGroups = [
        // ...

        'api' => [
            // ... other middleware group
            \App\Http\Middleware\JsonResponseApiMiddleware::class,
        ]
    ]

Method 2 :- set accept value as a application/json api requests only when any exception occurs, for this open Handler.php file at App\Exceptions directory. credit about this method 2

// App/Exceptions/Handler.php file

$this->renderable(function (Throwable $e, $request) {
    if($request->is('api/*') || $request->wantsJson()) {
        // set Accept request header to application/json
        $request->headers->set('Accept', 'application/json');
    }
});

Harsh Patel
  • 1,032
  • 6
  • 21
0

You need to set APP_DEBUG in you .env file to false.

or, if you use a phpunit, like follows

config()->set('app.debug', false);
Yevgeniy Afanasyev
  • 37,872
  • 26
  • 173
  • 191
-1
     /**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Throwable  $exception
     * @return \Symfony\Component\HttpFoundation\Response
     *
     * @throws \Throwable
     */
    public function render($request, Throwable $exception)
    {
        switch (class_basename($exception)) {
            case 'NotFoundHttpException':
            case 'ModelNotFoundException':
                $exception = new NotFoundHttpException('Not found');
                break;
        }

        return parent::render($request, $exception);
    }