In this lesson, we will set one correct header value to avoid returning web pages instead of JSON results.
As you can see in the /api/categories/1
, we see data for one category.
But what happens if we pass a missing category? We get an HTML page.
But for API, it is wrong. API expects the result as a JSON. In your client, set the headers to Accept
-> application/json
. Now Laravel knows to send the result as a JSON.
Another way is to set headers using Middleware. First, let's create a Middleware.
php artisan make:middleware AlwaysAcceptJson
Inside Middleware, we have a Request. Middleware is not just for preventing something; you can also add something to that request and pass that to the next request.
app/Http/Middleware/AlwaysAcceptJson.php:
class AlwaysAcceptJson{ public function handle(Request $request, Closure $next): Response { $request->headers->set('Accept', 'application/json'); return $next($request); }}
Now, we must register Middleware. Middleware can be registered to every Route or prepended to group web
or api
.
bootstrap/app.php:
return Application::configure(basePath: dirname(__DIR__)) ->withProviders() ->withRouting( web: __DIR__.'/../routes/web.php', api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware) { $middleware->prepend(\App\Http\Middleware\AlwaysAcceptJson::class); }) ->withExceptions(function (Exceptions $exceptions) { // })->create();
As you can see, with the disabled header in the client, we still get the same error in JSON format.
When working with APIs, remember to set headers.
Customize Error Message
When the error message is received, in this example, we have a message for the "Model not found".
No query results for model [App\\Models\\Category] 13
The [App\\Models\\Category]
part could be a security risk because a potential hacker could guess this is a Laravel project. Let's overwrite this message.
Exception can be added in the bootstrap/app.php
file withExceptions
method.
We need to render when the Exception is NotFoundHttpException.
We can check the error message exception
key to find which Exception to use.
We can return the custom message with a 404 status.
bootstrap/app.php:
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; return Application::configure(basePath: dirname(__DIR__)) ->withProviders() ->withRouting( web: __DIR__.'/../routes/web.php', api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware) { $middleware->prependToGroup('api', \App\Http\Middleware\AlwaysAcceptJson::class); }) ->withExceptions(function (Exceptions $exceptions) { $exceptions->renderable(function (NotFoundHttpException $e) { return response()->json(['message' => 'Object not found'], 404); }); })->create();
We can see our custom message.
This way, if your application also has a web version, then in the browser, users will see the same message instead of an HTML 404 error. To prevent that from happening, we must make a simple check.
bootstrap/app.php:
use Illuminate\Http\Request;use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; return Application::configure(basePath: dirname(__DIR__)) ->withRouting( web: __DIR__.'/../routes/web.php', api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) ->withMiddleware(function (Middleware $middleware) { $middleware->prependToGroup('api', \App\Http\Middleware\AlwaysAcceptJson::class); }) ->withExceptions(function (Exceptions $exceptions) { $exceptions->renderable(function (NotFoundHttpException $e, Request $request) { if ($request->wantsJson()) { return response()->json(['message' => 'Object not found'], 404); } }); })->create();
Request is a second parameter for a renderable callback function.
It may be worth adding the reason why the reportable method needs to be changed by the renderable one. In Laravel the report method is for logging or reporting exceptions, while the render method is for customizing the response sent back to the user when an exception occurs. By separating these concerns, Laravel allows you to handle exceptions in a flexible and organized manner.
return response()->json(['message' => env('APP_DEBUG')? $e->getMessage() : 'Object not found'], 404);
Just use this
@donilearn Thanks for a good snippet.
I think this code is little better. It achieves accessing env from config and getting full error stack.
$middleware->prependToGroup('api', [here the class is not launching])
Yes, me to
But we set middleware to always set headers application/json so if ($request->wantsJson()) kinda loses its purpose and if we remove middleware and user wont set header during api request , he will still get html... im a beginner and little bit confused.. but ill figure out something
Good lesson. There is a typo also:
prepented
n Laravel 12, when using $middleware->prepend('api', \App\Http\Middleware\AlwaysAcceptJson::class);, I get an HTML error: Target class [api] does not exist. I had to remove the first parameter from the prepend function to get a response for my HTTP request.
Replace $middleware->prepend('api', \App\Http\Middleware\AlwaysAcceptJson::class); by $middleware->prepend(\App\Http\Middleware\AlwaysAcceptJson::class);
For me, I normally use 'unauthorized' when in production and leave to not found in development
->withExceptions(function (Exceptions $exceptions): void { $isProduction = app()->isProduction(); $exceptions->render(function (Throwable $e) use ($isProduction) { if ($e instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) { $statusCode = $isProduction ? 401 : 404;