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 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:
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.
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!