Laravel: API Error Returns HTML and not JSON - How To Fix

If you launch an API request from a client like Postman, you may see an error in HTML and not JSON. How to prevent it?

Here's an example:

Let's dive into where it comes from. Here's our demo endpoint:

routes/api.php:

use App\Models\User;
 
Route::get('/user/{id}', function (Request $request, $id) {
return User::findOrFail($id);
});

After adding a few users to the DB, let's send a request to API using Postman, something like /api/user/4.

It works fine, and the model is cast to JSON automatically.

Now let's try to query a user which does NOT exist in our database:

See, HTML? Why not JSON error like "Model not found"?

The important thing there is the Accept header and its value.

The short version: you need to pass a Header Accept: application/json

More detailed explanation: the Accept request HTTP header indicates which content types the client can understand. The server uses content negotiation to select one of the proposals and informs the client of the choice with the Content-Type response header. Browsers set required values for this header based on the context of the request. For example, a browser uses different values in a request when fetching a CSS stylesheet, image, video, or script, but Postman accepts anything Laravel gives defined with a */* value. This is how we end up with HTML code in the response body.

This can be seen if you expand the hidden headers section.

Now let's try to add a header Accept: application/json. This tells the API endpoint that our client prefers to get data in JSON format.

That is exactly what we needed.

Notice: when APP_DEBUG=true your error messages will be very verbose, with all the stacktrace. On the production server, you set APP_DEBUG=false in your .env file, and then you will see the "shorter" version of the error, without the full stacktrace.


What about testing API in Browser?

Another common habit that beginner developers tend to do is to test their API endpoints using the browser.

If we hit the /api/user/2 URL we can see that it is displayed as expected.

But what happens if a user is not found? This can be tested by querying for the user that does not exist.

We encounter the same problem, we get a 404 Not Found status response. Because the browser does not pass the Accept: application/json header, it renders HTML code from the response.

This is the reason it is highly discouraged to test API endpoints using the browser.

The right thing to do is to use tools designed to build APIs such as:

And of course, be aware of the Accept: application/json header in requests.

avatar

Hi! Just to share something from the Laravel-tips repository. If you don't want to add the header Accept: application/json to every postman request or another app/pluguin/etc that you are using. It really helps to have a middleware for api routes to force that header. (Or any header if you want to).

Exampe and source of the middleware tip: https://github.com/LaravelDaily/laravel-tips/blob/master/api.md#force-json-response-for-api-requests

👍 3
avatar

good tip, although, then you hit another app and same thing occurs. sometimes local fix is a way to go, but you need to understand the cause of a problem.

avatar

Have you ever had a problem when the API sporadically returns first response as code 200 text/html, even if it's not supposed to? Any subsequent response is a correct one.

I've tried catching the controller return value - it looks correct. I've disabled caching in nginx - but that didn't change anything...

avatar

The only time I've had this - was when incorrect headers were sent. But not sure, this is a bit hard to debug without code and takes a lot of time

avatar

Fixed my issue, but thanks for the reply! In my case - somehow the gzip compression in the nginx config was returning every first and then every 3rd response incorrectly. No gzip - no problems! I guess I'll have to dig deeper in nginx docs now...

Like our articles?

Become a Premium Member for $129/year or $29/month
What else you will get:
  • 59 courses (1057 lessons, total 42 h 44 min)
  • 80 long-form tutorials (one new every week)
  • access to project repositories
  • access to private Discord

Recent Premium Tutorials