Implicit fancy 404 json instead of ModelNotFoundException in laravel API
Updated: 23th April 2025
Tags: laravel
Note: this article for laravel 11, laravel 12. Other versions not tested.
For lazy artisans like me that use code like this:
<?php
$user = User::query()->findOrFail(1);
You will notice API is returning
{
"message": "No query results for model [App\\Models\\User] 1"
}
Well it is nice, but I want it to say User not found
.
So we will redefine response for api that has ModelNotFoundException
Edit bootstrap/app.php
file, find ->withExceptions(function (Exceptions $exceptions) {
and make it like this
<?php
// bootstrap/app.php
// .....
->withExceptions(function (Exceptions $exceptions) {
$exceptions->render(function (NotFoundHttpException $e, Request $request) {
if ($request->is('api/*') && ($e->getPrevious() instanceof Illuminate\Database\Eloquent\ModelNotFoundException)) {
$model = Str::afterLast($e->getPrevious()->getModel(), '\\'); // extract Model name
return response()->json(['message' => $model.' not found'], 404);
}
});
....
Full bootstrap/app.php
should look similar to this:
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
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): void {
//
})
->withExceptions(function (Exceptions $exceptions) {
$exceptions->render(function (NotFoundHttpException $e, Request $request) {
if ($request->is('api/*') && ($e->getPrevious() instanceof Illuminate\Database\Eloquent\ModelNotFoundException)) {
$model = Str::afterLast($e->getPrevious()->getModel(), '\\'); // extract Model name
return response()->json(['message' => $model.' not found'], 404);
}
});
})->create();
See this line:
<?php
$model = Str::afterLast($e->getPrevious()->getModel(), '\\'); // extract Model name`.
This will grab default message and extract model name from it. So we will get User not found
, Product not found
, etc message implicitly like true artisans.
For example, you have model Item777
. But you want users to see Fancy product
. So you go fancy mode:
<?php
$model = Str::afterLast($e->getPrevious()->getModel(), '\\'); // extract Model name
$fancyNames = ['Item777' => 'Fancy product', 'User' => 'Customer'];
$fancyName = $fancyNames[$model] ?? $model;
return response()->json(['message' => $fancyName.' not found'], 404);
Or you can return generic response and keep it obscure:
<?php
return response()->json(['message' => 'Record not found. Go away now!'], 404);
The only thing you need to do is to write ->findOrFail($id)
or if you are inside $user, $user->products()->firstOrFail()