Back to Course |
Handling Exceptions and Errors in Laravel

Handling Exceptions in Laravel API

Working with API often means that "lazy" front-end developers show the error messages on the page/screen exactly as you return them from the back end. And in Laravel, some automatic Exception messages are not that informative:

Let's see how we can improve this with quick fixes.


Handling Exceptions in API

Handling exceptions in the API is not that different from a typical Laravel web project. The only problem is Laravel will automatically return the Exception message, which may not only be confusing but also result in a potential data leak:

A few things are wrong here:

  • This message is not user-friendly and should be visible only to the developer
  • This message returns the ID you were trying to access. It is a potential data leak
  • This message returns the Model class with the namespace. It could be treated as a data leak

The data leaks above may be a security issue, as someone trying to call our API maliciously would find out the structure of our IDs or namespaces.

So, how can we fix this? Well, one of the documented ways is to use a different render method, with $this->renderable():

Controller

use App\Models\User;
use Illuminate\Http\Request;
 
// ...
 
public function __invoke(Request $request)
{
$user = User::findOrFail($request->input('user_id'));
 
// ...
}

app/Exceptions/Handler.php

use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
 
public function register(): void
{
// ...
 
$this->renderable(function (NotFoundHttpException $exception, Request $request) {
if ($request->isJson()) {
return response()->json([
'message' => 'Resource not found'
], 404);
}
});
}

This code will allow us to handle the exceptions and return a user-friendly message globally:

Yet, this comes with another issue - this will handle all of our 404 responses. For example, if we try to access an endpoint route that does not exist, we will get the same message:

This flow is pretty bad and can produce unwanted results later on.


Using try-catch with Exceptions

In this case, I suggest not relying on the general Laravel Exception Handler but trying to catch a specific Exception ourselves. We're looking specifically for ModelNotFoundException here.

Controller

use App\Models\User;
use Exception;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Request;
 
// ...
 
public function __invoke(Request $request)
{
try {
$user = User::findOrFail($request->input('user_id'));
 
// ...
} catch (ModelNotFoundException $modelNotFoundException) {
return response()->json([
'message' => 'User not found'
], 404);
} catch (Exception $exception) {
return response()->json([
'message' => 'Something went wrong'
], 500);
}
}

This code will allow us to handle different Exceptions and return a user-friendly message. Each type of Exception here can get its message and status code.

As you can see, we have set a different message in case we don't find the user. Yet, if anything else fails - we will return a generic message. And this is very expandable!

public function __invoke(Request $request)
{
try {
$user = User::findOrFail($request->input('user_id'));
 
// ...
} catch (ModelNotFoundException $modelNotFoundException) {
return response()->json([
'message' => 'User not found'
], 404);
} catch (IncompleteDataException $incompleteDataException) {
return response()->json([
'message' => 'User has incomplete data'
], 500);
} catch (NoAccessException $noAccessException) {
return response()->json([
'message' => 'Current user has no access to this user'
], 403);
} catch (Exception $exception) {
return response()->json([
'message' => 'Something went wrong'
], 500);
}
}

Of course, it introduces more code in Controller, but it's a lot more flexible and allows us better control over what API returns.


So, that's it for this course. Good luck handling errors and exceptions in your Laravel projects!