DEV Community

Marco Damaceno
Marco Damaceno

Posted on

404 status code! Really?

Hi, guys! This is my first post on Dev.to.

I come to share with you a thought that intrigues me a little. Maybe, a little strange one at first sight.

Well... I have built a REST API for a personal project and worked on a endpoint that retrives a single resource. For instance:

GET /books/8c2ba535-5523-47a5-8a72-281c316d5fc4
Enter fullscreen mode Exit fullscreen mode

As you can see, the resource can be found by an UUID. This URI is mapped as GET /books/:id.

The thing is this UUID does not represent any record in the books table in the database. So, it returns nothing.

It is common to deal with this situation returning a 404 (not_found) status code that indicates an error from the client as specified on RFC 7231 section 6.5.4

The 404 (Not Found) status code indicates that the origin server did not find a current representation for the target resource or is not willing to disclose that one exists.

I have dealt with it as an untouchable law until reading what is specified on RFC 7231 section 6.3.1:

The 200 (OK) status code indicates that the request has succeeded. The payload sent in a 200 response depends on the request method. For the methods defined by this specification, the intended meaning of the payload can be summarized as:
GET a representation of the target resource;

This definition for 200 status code makes me believe that returning a payload with null is correct because it is what exactly the client requested! The client just sent an ID that does not represent any record, the structure of the URI is fine though. Thus, the client gets a success response, but with a null as a resource.

According to this logic, the payload would be like:

{
  "data": {
    "book": null
  }
}
Enter fullscreen mode Exit fullscreen mode

For the status code to be 404, the request would be like:

GET /boocs/8c2ba535-5523-47a5-8a72-281c316d5fc4
Enter fullscreen mode Exit fullscreen mode

boocs really does not exist.

All of this makes me think the way we handle this situation depends on how we deal with the role of the database in the application.

Does the database have an active participation on the application interfering in the business rule? Go for 404 status code. Eg.: a private repository hosted on Github being accessed by an unauthorized user.

Is the database just a data repository? I tend to believe the response would have a success payload with null. Eg.: the majority of the APIs worldwide.

Summing up:

"If you have nothing to give, give nothing. If you do, decide if you want to give something, (don't) give it and communicate."

Crazy, uh?

I know there already are some talks about this on the internet, but I think it worths sharing my thoughts with you and I'd like to know about your opinion.

What do you think about?

Discussion (20)

Collapse
ben profile image
Ben Halpern

I think the "principle of least astonishment" would dictate 404 to me personally, but this is definitely a juicy topic!

Collapse
scottshipp profile image
scottshipp

IMO 404 all the way 100% o long as we're talking about REST.

The whole REST architectural style is predicated on the idea that the state of a resource at any given moment is temporary:

The key abstraction of information in REST is a resource . . . A resource is a conceptual mapping to a set of entities, not the entity that corresponds to the mapping at any particular point in time.

Source: Fielding dissertation 5.2.1.1

The idea of the current resource state is hinted at in the immediately following line of the referenced RFC 7231 section 6.5.4

A 404 status code does not indicate whether this lack of representation is temporary or permanent

In other words: the resource does not exist in this API at this moment. However, it might one day. Who knows. But it is 404 Not Found right now.

Reading just one sentence further in the definition of 404 in RFC 7231, you find out that there's a response code for if the resource was there at one point, and is now permanently no longer there:

the 410 (Gone) status code is preferred over 404 if the origin server knows, presumably through some configurable means, that the condition is likely to be permanent.

This indicates the shared understanding of the author of the RFC of a resource as the mapping to an entity. The entity changes, it has state, but the mapping does not. The mapping identifies that entity permanently.

So to address the issue raised in the above article:

This definition for 200 status code makes me believe that returning a payload with null is correct because it is what exactly the client requested! The client just sent an ID that does not represent any record, the structure of the URI is fine though. Thus, the client gets a success response, but with a null as a resource.

The main reason I believe this is not an accurate interpretation is that the mapping does not exist. And since the resource is the mapping, 404 is the only logical decision.

Besides that, there's already an implicit understanding that if you receive a response, "the structure of the URI" is fine.

Finally, at the end of the day, I think it logically doesn't make sense to tell API consumers 204 No Content or 200 with a null payload, because you would make them think you do have a resource for that given ID within your API, but you're just storing all null values.

This semantic confusion would create similar issues to hash table data structures in some languages that return null values for keys that aren't in the table. Does the null response mean that the key is in the table and currently has the value null? Or does it mean the key isn't in the table? Same problem, and best to avoid.

Collapse
simonlegg profile image
Simon

AHHHH I’ve flipped my opinion on this so many times just on this page!! I have no idea, I think either is ok as long as an API is consistent it’d be annoying if one endpoint returned a 404 if the record didn’t exist and another endpoint in the same API returned a 200 with null data.

I think as long as they’re consistent either is totally fine. I think. Or do I?! I just don’t know anymore 🤯

Collapse
fferegrino profile image
Antonio Feregrino • Edited on

Why add the burden of checking whether the response comes back with a null or not to the developer that uses your API?

501 Not Implemented or 405 Not Allowed, for endpoints that do not exist (boocs, in your case)
404 Not Found for resources that do not exist (or the user has no access to)
200 OK for succesful queries that return data

Collapse
mdamaceno profile image
Marco Damaceno Author

If the database is part of your application, go ahead with 404. If not, why to lean your app on it? It's just another perspective of how to look at the application. The RFC keeps ruling.

Collapse
phlash909 profile image
Phil Ashby

If a resource is requested with an invalid ID, then either:

  • the client (human or machine) has made an error, 4xx required
  • the server has made an error (lost the record), 5xx required

I would be very wary of responding with a 2xx code, as the caller may well assume they have what was asked for and misbehave.

That said, I recommend RFC7807 error messages should always accompany an error response, to add machine-readable nuance that status codes do not permit: tools.ietf.org/html/rfc7807 :)

Collapse
elmuerte profile image
Michiel Hendriks
GET /books/8c2ba535-5523-47a5-8a72-281c316d5fc4

Returns a 404, because the URI points to something which does not exist

GET /books?id=8c2ba535-5523-47a5-8a72-281c316d5fc4

Returns a 200, with a null response because it's a query for a record which does not exist.

GET /boocs/8c2ba535-5523-47a5-8a72-281c316d5fc4

Returns 400 with this body

Collapse
emman27 profile image
Emmanuel Goh

Going to disagree with this as it's a leaky abstraction, exposing the unnecessary detail of having a database to the calling client.

Whether or not a database is queried to check if the book in fact exists or whether all books are simply static pages (though that's often not feasible) should not actually matter to the client. Unless the API is able to reply the other path of GET /boocs/8c2ba535-5523-47a5-8a72-281c316d5fc4 with a similar response such as

{
  "data": {
    "booc": null
  }
}

then I don't think there's actually full consistency and a clear abstraction being made

Collapse
buinauskas profile image
Evaldas Buinauskas • Edited on

404 all the way. It's client error because it didn't supply correct ID.

If a message is needed, RFC suggests using object type called problem details. It is standard type to transfer error messages and it can be extended to contain additional information 🙂

My rule of thumb is that an endpoint should only have a single successful response type and code.

Collapse
ravavyr profile image
Ravavyr

The docs say: developer.mozilla.org/en-US/docs/W...

So Sorin's comment 204 - no content kind of makes sense.
Then i think, well what if that book exists, but is just currently not available?
Getting a blank "no content" doesn't tell you what's actually wrong with your request or with the data.

Ben's note about a 404 is also what's often taught these days as far as error responses go and you can put a message in the response header itself [which almost everyone i know doesn't even know is possible so they completely overlook it]

Personally, I hate it when an endpoint returns a generic response header with no message on error.
*Because you don't know if the endpoint is failing, or you passed in the wrong path, or the data requested doesn't exist. *

I much prefer a 200 response: The request has succeeded
[so you know the endpoint works and received your info correctly]
It should respond with a response object with status: failed and msg: "No go buddy: because x y z" that tells you exactly what's wrong with your request info, since the server was successful in processing it and respond.
This way you know what to do to fix it.

Collapse
sorincostea profile image
Sorin Costea

What you're looking for is called "204 - No Content"

Collapse
mdamaceno profile image
Marco Damaceno Author

Hi Sorin Costean!

I don't think so. If you have an endpoint to get a list of books and make a request to it. What would it be that response? A empty array with 200 status code or 204 with no content? I think the first response fits more the client expectations than the second one. Why would it be different for a single resource?

Collapse
rafaacioly profile image
Rafael Acioly • Edited on

Marco, what i usually do is

If the request came on a list endpoint and there's no content i return 200 with a empty list, if the request is for a single record and the record doesn't exist i return 204.

Normally my "pagination" responses retrieve another useful data as well like "next_page", "count" and etc, so it makes easy for validation if the response is "200".

Thread Thread
mdamaceno profile image
Marco Damaceno Author

An empty list is no content as well. Why not use 204 for it?

Thread Thread
pchinery profile image
Philip

If you have no items for the list, returning an empty list will yield the full available content. Maybe you will also add some metadata (e.g. total number of entries) to support pagination. Your consumer will not have to treat this differently from a filled list. When requesting a single item that does not exist, users would have to examine the content and see if they can work with it. And from my experience: if you start seeing surprising responses, the trust in the responses from this system drops rapidly.

Collapse
pchinery profile image
Philip

This is something that depends on the interpretation of the RFC as well as the intended "protocol". If you want to be RESTish, the book with the given id is a resource and if that resource does not exist, The best response in my opinion is the 404. Giving a 200 with a null or empty response is more or less an encoding trick. The consumer will have to check the content for certain criteria first. I'll admit that checking the status code is nearly the same check, but why would I want to throw away this means of expressing something about the result and replace it with something that is not defined in the RFC? I'm my opinion, that's the way of communicating clearly.

When you imply that you don't see the database as a part of your application and rather a data repository: I totally agree, but I draw different conclusions from this. Even though the database will know whether a book exists, it does not really define whether it exists. This definition is done by the users of your system when they create books. They will not care where it is stored though, as long as no data will be lost. And if we assume that the database is consistent, a user asking for a book (represented by an UUID) that does not exist is asking for something that really does not exist. It's not just a syntactical error in the user's side. So I still think a 404 is the way to go here.

Having said that, I also have my problems with the RESTish approach of CRUD operations, as we will often want to restrict which changes a user can do at a given time in real-world applications. I do prefer going with a more CQRS-like way of having commands to perform certain operations in a resource. But that would open another large discussion.

Collapse
denny1jacob profile image
Denny1Jacob • Edited on

Returning empty for "data not found" forces the client to check both return status and the content returned. This approach does to save the client from checking the return status because they need to check for errors resulting from environmental faults anyways.

"If you have nothing to give, give nothing [...]" is correct in the form of a "not found status", not in the form of a made up content albeit empty. Besides, can the client use an empty payload in all cases when it is expecting a predefined model in the content? It is just pretzel programming to torpedo established client expectation.

Collapse
blessanm86 profile image
Blessan Mathew

Was getting bit by this recently and Im also a bit confused on the right approach. There is the spec that everyone keeps talking about. But from a FE perspective, there is so many times I do these kinda GET apis, where I show or hide UI based on the response.

When the server sends 404, it goes into my generic error handling code. Now I need to write conditions there to tell it to ignore this 404 cause my UI knows how to render this response.

I would have preferred the server return 2xx for these situations so that my generic error handling is there to handle actual errors.

Also it creates a lot of noise in the devtools and error tracking tools when you expect certain api's to not have a response.

Maybe something like Axios interceptors will help for my first problem.

Collapse
diegomgar profile image
Dieg Oto

I prefer 404 too