Struthious
Struthious

Reputation: 85

Changing default error message when record not found - Laravel API

I am building a simple API, which there is a point that when the ID entered in the endpoint URL does not point to a valid record, I get a standard NotFoundHttpException. And I cannot figure out how to override this in order to provide my own error message response as I do not wish to share my Model name etc.

Endpoint Route::get('{mrl}', [MrlController::class, 'show']);

Controller

public function show(Mrl $mrl)
    {
        if ($data = $mrl) {
            return response(['status' => 'ok', 'data' => $data], 200);
        } else {
            return response(['status' => 'error', 'message' => 'Could not retrieve data'], 500);
        }
    }

When I run this when a record exists I receive the following which is what I expect.

{
    "status": "ok",
    "data": {
        "id": 98,
        "market_id": 1,
        "crop_id": 2,
        "chemical_id": 113,
        "maximum_residue_level": null,
        "exempt": 0,
        "comments": null,
        "date_verified": "2021-10-07",
        "created_at": "2021-10-19T05:42:12.000000Z",
        "updated_at": "2021-10-19T05:42:12.000000Z"
    }
}

However, when I enter an ID in the route endpoint for a record that does not exist, I receive the following:

{
    "message": "No query results for model [App\\Seasonal\\Mrl] 99"
}

This is happening from what I understand to be the auto find of the record between the Route and the controller, and I am lost as to how to customize this behavior.

Thanks in advance for your help!

Upvotes: 1

Views: 1752

Answers (2)

Struthious
Struthious

Reputation: 85

After spending time reading the docs I finally found the answer on how to customize the behaviour if a record is found when utilising Eloquents Route Model Binding.

In the Laravel docs, there is a method for this explicit purpose, to override the default behaviour when the bound model does not have a valid record.

The method for doing this is to chain the ->missing() function call to the end of your route and then providing the behaviour for the response you wish to provide. As below:

Route::get('{mrl}', [MrlController::class, 'show'])->missing(function () {
    return response(['status' => 'error', 'message' => 'Invalid query.'], 404);
});

Chaining this method onto my request has enabled me to return the generic response I was hoping for when querying an invalid model record.

Desired response achieved

Upvotes: 3

YannPl
YannPl

Reputation: 1322

you don't show the code where you are fetchning the model from the database but we can assume something like that:

$mrl = Mrl::findOrFail($id);
show($mrl);

The model findOrFail() method throws an exception when the model is not found, which is convenient when you want to adapt the response.

You can imagine something like that:

try {
    $mrl = Mrl::findOrFail($id);
    return response(['status' => 'ok', 'data' => $data], 200);
} catch (ModelNotFoundException $e) {
    return response(['status' => 'error', 'message' => 'Could not retrieve data'], 500);
}

The idea is to catch the error thrown by your model to change the message and status code of the response.

When building APIs you should event add a "generic" catch statement for any unhandled errors to display a standardized generic error message and log what happened, like this:

try {
    $mrl = Mrl::findOrFail($id);

    // Do more things that could generate errors ?

    return response(['status' => 'ok', 'data' => $data], 200);
} catch (ModelNotFoundException $e) {

    // Not found
    return response(['status' => 'error', 'message' => 'Could not retrieve data'], 500);
} catch (\Exception $e) {

    // Generic error
    \Log::error($e);
    return response(['status' => 'error', 'message' => 'An error occured'], 500);

}

Upvotes: 1

Related Questions