HTTP status codes are like short messages returned from a server whenever we request or interact with a resource on the server. They are an invaluable asset for diagnosing application errors and help to fix them quickly.
Each HTTP status code has a specific and unique meaning, however, there are cases where developers might confuse the purpose of a status code with another status code with almost similar usage. In this article, we will be discussing some of the common mistakes that developers make when using HTTP status codes.
HTTP Status Codes For Invalid Data: 400 vs 422
To understand the difference between these two status codes, let’s assume a case where you pass the string value “password” for the key password in the request body but the developer has blacklisted this string value on the server side to prevent users from using this as their password. In this case, what should be returned as the response status code: 400 or 422?
If you were thinking 400, in that case, let’s think it out loud once. You have passed a string value to the API endpoint that expected a string value, but the value of the string contained data that has been blacklisted or we can say, isn’t satisfying the condition for further processing on the server. Hence, the syntax of the request isn’t wrong, so, we can’t return a 400 status code saying bad request. According to w3.org, “400 Bad Request” means:
The server was unable to understand the request due to incorrect syntax. The client SHOULD NOT REPEAT the request UNALTERED.
Therefore, we can use the status code “422 Unprocessable Entity” in this scenario. That would mean that the server understands what you are trying to do, and it understands the data that you are submitting, but it simply won’t let that data be processed further.
The 422 (Unprocessable Entity) status code indicates that the server recognized the requested entity's content type but was unable to process the instructions contained within it.
The status code 422 can be returned in cases such as an error condition where the XML request body contains well-formed and syntactically correct XML but it is semantically erroneous.
Handling Forbidden RESTful Requests: 401 vs 403 vs 404
To explain this issue, say we have two users in our system: Sam with ID 4 and Rick with ID 7. Assuming Sam attempts an authorized API call to view Rick's profile resources, as shown below:
GET /users/7/profile HTTP/1.1
Authorization: Basic YmVuK2F206dGVzdA==
Accept: application/json
Here, Sam is using Basic Authorization to identify himself as Sam, but he's making a call to access another person's profile, and let's suppose that a user can only read his or her own profile in this application.
So the question here would be what status code should be returned? The most appropriate ones could be:
Sam is not authorized to view Rick’s profile, so “401 Unauthorized”?
Sam is forbidden from viewing someone else’s profile, so “403 Forbidden”?
Sam can’t simply see resources that he’s not allowed to see, so “404 Not Found”?
However, if you return 401 or 403, you will be disclosing secure information since, in both circumstances, we are declaring that the resource exists but you can't view it, thereby verifying the resource's existence. So, can we use 404 in this case? Let's see what happens.
The description of “403 Forbidden” says that:
The server comprehended the request but refused to perform it. Authorization is useless and the request SHOULD NOT BE REPEATED.
If the request method is not HEAD and the server intends to make public the reason why the request was not completed, the entity SHOULD specify the reason for the rejection.
If the server does not want the client to get this information, the status code 404 (Not Found) might be used instead.
Well, this should solve the problem. If you don’t want to expose the information, you should return a “404 Not Found” instead.
Empty resource HTTP status code: 200 vs 204 vs 404
To illustrate this, imagine there is a method GET /users that returns a list of all users and GET /users?name=sam provides a list of only users with the name sam. What HTTP status code should be issued if there is no user named Sam?
The first and most likely option is 200 Success, as the request will be successful and an empty list will be returned. It might be claimed that the /users collection/list resource exists and that the name query param is used to filter the list's content.
However, the “204 No Content” status code definition says:
The 204 (No Content) status code indicates that the server has successfully fulfilled the request and that there is no additional content to be sent in the response payload body.
While 200 OK is a common and acceptable answer, 204 No Content may make sense if there is nothing to return. It is most typically used in response to a PUT (replace) or a PATCH (partial update) when servers do not wish to deliver the replaced/updated resource, or in reaction to a DELETE, because there is usually nothing to return after a deletion. It may, however, be used on a GET.
If the request is valid, has been correctly fulfilled, and there is no more information to send (which is the case because the returned list is empty), the answer 204 No Content is understandable and valid.
However, the 404 Not Found status code’s definition says:
The 404 (Not Found) response code indicates that the origin server was unable to locate a current representation of the requested resource or is unwilling to reveal the existence of one.
Assuming that /users is the resource used even when doing a GET /users?name=sam, and obtaining a 404 HTTP status code makes no sense because the resource /users exists, it's only that the list it contains may be empty.
This concludes this article; please share any additional use cases in which you encountered confusion and how you resolved it. I hope you found this article useful.
Keep reading!
Top comments (21)
I use HTTP 200 everywhere because Facebook says it's the best practice now #graphql
eeew
Source please?
If you are doing REST, you can ignore my snarky comment and read the fine article instead.
For GraphQL specifically, Facebook took the somewhat controversial decision to throw away all HTTP codes and verbs and server-side caching mechanisms. Everything is a
POST { "query": "graphql query", variables: { json } }
to example.com/graphql and returnsHTTP 200 OK
. Exceptions and failures are treated via another mechanism. Caching... who needs caching?See for example 200 OK! Error Handling in GraphQL and serving GraphQL over HTTP
Thanks for sharing this @jmfayard, I have only worked with REST APIs, so this is mostly about it, but I am glad that now the readers can understand the context for GraphQL from your comment as well.
@jmfayard isn't a world without errors the one we want? 😂
At the end is a design decision and you can use it or not. Apollo sure has some errors available. Got your point anyway 😁
I think the way graphql handle errors is surprising at first but fine in the end.
On the other hand the decision to just disregard the built-in HTTP server side caching is IMHO very questionable.
Depending on your use case, that may be a reason to prefer to stick to REST.
In an endpoint-based API, clients can use HTTP caching to easily avoid refetching resources, and for identifying when two resources are the same.
The URL in these APIs is a globally unique identifier that the client can leverage to build a cache.
In GraphQL, though, there's no URL-like primitive that provides this globally unique identifier for a given object. It's hence a best practice for the API to expose such an identifier for clients to use.
source
You can extend that to specific fields easily instead caching the whole response, see example
Hello Pragati Verma,
thank you for your article.
It's easy to read and the examples you give are easy to understand.
Thank you. I am glad that you liked it :D
Great share! 💯
@pragativerma18
Thank you. I am glad that you liked it 😁
Some people argue that query is part of the resource: en.wikipedia.org/wiki/URL
Plus using
/users?name=sam
you're seeking for a given resource (a user who's name is sam) and if there is none, then the resource could not be found, hence the 404 - Not Found.On the other hand, if you request for
/users
I totally agree on receiving an empty array as response.Your article is good.
It shows a way to consider http code.
Unfortunately, it's very complicated 😁
Mostly because it depends on what you do, and who uses your API.
When working on an REST server, I would say that everything you said is OK.
But, you could also consider that http code were not designed for this. So any implementation could be ok.
I would say that http codes are simply things of an other era from the beginning of internet.
When using a rest server, the only thing that matters is the error codes you returns.
So for my point of view, as long as you define a pattern such as:
Someone is consuming your API, the way some libraries are working lead to problems when the server returns something else than a 2XX. I'm thinking about JavaScript here. You can count on developers to handle the success, but not the errors. It will lead to JavaScript app that will stale because they didn't handle the fact that you may return the fact the password can be unaccepted.
Also please consider that Google Chrome or Firefox will report non 2xx as errors. Some of your clients may report you this as a problem because they appear in red in the console.
People will have to handle the errors you return. I18n matters.
Http codes won't be enough, so I would say the only thing that matters is the error code you return.
I can claim the opposite: a poorly written library won't handle the response correctly and will just look at the response status code. Have fun debugging that. The response claims everything is okay, the error won't pop out on you in HTTP logs, and the client library may end up passing garbage data to your application (think of all the times dealing with "Cannot read properties of undefined" error).
In fact, JavaScript's
fetch
will fail only due to network error, it won't throw on4xx
or5xx
status codes. If you are opting into a wrapper library which treats different status codes as errors, maybe you should also opt into a correct error handling.Personally, I don't see status codes as a relic of the past, but as a useful tool for handling common situations in API communication. Sure, status codes don't handle all the scenarios by themselves, but there you can use a detailed response. There's even RFC 7807 suggesting a standardized format for errors in HTTP APIs.
Anyway, no matter how you approach HTTP status codes in your API, just make sure to properly document the error responses. It will save your users a lot of pain.
I concede. I'm not using JavaScript, I only know developers who does. And I know they faced problems few years ago because of that.
Thanks for sharing the rfc, I will read it.
Once again, http codes can be considered in multiple ways. None is invalid.
Facebook move to almost always send 200 is not stupid. It's a choice.
Http codes as error codes are only a convention between who consumes your API and you. Everything is vid as long it's documented.
I will read the rfc you mentioned, thanks for sharing
Your definition of error 400 is outdated, it corresponds to the RFC 2616 (1999) which was replaced by RFC 7231 (2014) then RFC 9110 (2022) which defines error 400 as follows :
Based on this definition, a 400 is perfectly acceptable in the case of a well-formed request body containing an unacceptable value. Furthermore, error 422 is basically adapted to the WebDav context (although this no longer seems to be the case in RFC 9110)
Ohh, great article! I don’t know how many times I’ve discussed whether to use 401 or 403. it’s really helpful, to look into the specs. Especially that part that says „you must not retry the request unaltered“ in some cases helps to understand whether or to use one or the other.
Also, returning 404 instead of 403 make sense, this is definitely something I have learned today.
Regarding the 422, it makes sense but I have never seen any API in the wild that actually uses that. I would think that most 400 responses should actually be 422 according to the specs.
500 for everything , except when its 200, effectively a bool
I'm kidding, its sarcasm.
Exactly what I was looking for 😎🙏🏾