DEV Community

Cover image for You're Not Using HTTP Status Codes Right
Pragati Verma
Pragati Verma

Posted on • Updated on • Originally published at hackernoon.com

You're Not Using HTTP Status Codes Right

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
Enter fullscreen mode Exit fullscreen mode

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!

Latest comments (21)

Collapse
 
luiz0x29a profile image
Real AI

500 for everything , except when its 200, effectively a bool

I'm kidding, its sarcasm.

Collapse
 
joulss profile image
Joulss • Edited

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 :

The 400 (Bad Request) status code indicates that the server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).

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)

Collapse
 
ryzorbent profile image
Thabo

Exactly what I was looking for 😎🙏🏾

Collapse
 
stealthmusic profile image
Jan Wedel

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.

Collapse
 
ccoveille profile image
Christophe Colombier

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:

  • the end point doesn't exist: 404
  • the server cannot handle what you sent, because you send an XML and the server handles Jason only. => 400
  • everything else could be a 200
    • you are creating/updating/deleting a resource (when you talked about using 204), return a simple "ok:true" or "acknowledged:true"
    • the password cannot be accepted => 200 + error code about password that cannot be accepted
    • you search something that do not exist => 200

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.

Collapse
 
jnv profile image
Jan Vlnas

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.

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 on 4xx or 5xx 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.

Collapse
 
ccoveille profile image
Christophe Colombier

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

Collapse
 
boanjfm profile image
Bo Andersen

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

Some people argue that query is part of the resource: en.wikipedia.org/wiki/URL

Collapse
 
joelbonetr profile image
JoelBonetR 🥇 • Edited

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.

Collapse
 
devgancode profile image
Ganesh Patil

Great share! 💯
@pragativerma18

Collapse
 
pragativerma18 profile image
Pragati Verma

Thank you. I am glad that you liked it 😁

Collapse
 
incrementis profile image
Akin C.

Hello Pragati Verma,

thank you for your article.
It's easy to read and the examples you give are easy to understand.

Collapse
 
pragativerma18 profile image
Pragati Verma

Thank you. I am glad that you liked it :D

Collapse
 
jmfayard profile image
Jean-Michel 🕵🏻‍♂️ Fayard

I use HTTP 200 everywhere because Facebook says it's the best practice now #graphql

Collapse
 
luiz0x29a profile image
Real AI

eeew

Collapse
 
dendihandian profile image
Dendi Handian

Source please?

Collapse
 
jmfayard profile image
Jean-Michel 🕵🏻‍♂️ Fayard • Edited

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 returns HTTP 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

Thread Thread
 
pragativerma18 profile image
Pragati Verma

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.

Thread Thread
 
joelbonetr profile image
JoelBonetR 🥇

@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 😁

Thread Thread
 
jmfayard profile image
Jean-Michel 🕵🏻‍♂️ Fayard • Edited

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.

Thread Thread
 
joelbonetr profile image
JoelBonetR 🥇
On the other hand the decision to just disregard the built-in HTTP server side caching is IMHO very questionable.

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