Take a look at the following code. It's a jQuery $.ajax
request asking for a cast member of The Office.
const message = document.querySelector('#message');
$.ajax({
url: "/status/Michael",
dataType: "json",
success: (data) => {
if (data.error) {
alert(data.error);
} else {
message.innerHTML = data.message;
}
}
});
If the response includes an error property, the request is considered an error and we alert the user. Otherwise, a message is shown about the character.
This works. However, it's a little contradictory and some might consider it a code smell. After all, we're handling errors inside the success
callback. This is a clue that something should likely be changed on the server.
Since we have to handle both cases within the success
callback, we have a fairly good idea the server is returning a 20x
status code for both successful and bad requests. Taking a peek at the server, this is what we might see:
class StatusesController < ApplicationController
CHARACTERS = ["Michael", "Dwight", "Angela"]
def show
if CHARACTERS.include?(params[:name])
render json: { message: "#{params[:name]} is a character on The Office." }
else
render json: { error: "Oops. There's an error." }
end
end
end
This is the JavaScript we would like to write. We'd like to use both a success
and an error
callback. This code reads a little better now. We can clearly see what happens when a request is successful or a request has an error. We don't have to concern ourselves with a conditional.
const message = document.querySelector('#message');
$.ajax({
url: "/status/Michael",
dataType: "json",
success: (data) => {
message.innerHTML = data.message;
},
error: (jqXHR) => {
alert(jqXHR.responseJSON.error);
}
});
Once the server returns a 404 Not Found
, we can start using the error
callback. We might update the server to look like this. Notice how we've added a status to both cases.
class StatusesController < ApplicationController
CHARACTERS = ["Michael"]
def show
if CHARACTERS.include?(params[:name])
render json: { message: "#{params[:name]} is a character on The Office." }, status: :ok
else
render json: { error: "Oops. There's an error." }, status: :not_found
end
end
end
Previously, as we know, the server was returning a 200 OK
status code for both cases. After our update, :ok
refers to the status code of 200 OK
while :not_found
refers to 404 Not Found
. This allows our $.ajax
call to route the response to the corresponding callback based on the response's status code.
This short example illustrates the importance of status codes and how a status code can affect anyone developing against it. When you're thinking about the implementation for a new endpoint or route, take a moment to think about what status code makes sense. A status code can have an effect on many things.
What to watch out for
Other developers
As you saw in our example, a status code can have an influence on how a frontend developer builds against the backend. Oftentimes, we need to fetch data from the backend and the status codes can influence how this is done.
If a status code is unexpected, you might see a weird bug. If we didn't know our server was returning a 200 OK
for both successful and bad requests, our customers might run into interesting scenarios.
Security
Picking the wrong status code can provide clues to an attacker. For example, returning a status code other than 404 may give an attacker a clue that a given resource exists on the server.
Take a look at this excerpt from the GitHub API documentation on authentication.
There are two ways to authenticate through GitHub API v3. Requests that require authentication will return 404 Not Found, instead of 403 Forbidden, in some places. This is to prevent the accidental leakage of private repositories to unauthorized users.
If we give out clues that a private resource exists, we begin to lose trust with our customers.
SEO
If you have public endpoints, be careful when changing the status code. Using the wrong one can yield unexpected results.
When introducing a redirect, it's considered best practice to use 301 Moved Permanently
. A search engine will pass along any link juice to the new URL. If you use a 302 Found
, it might not.
Take a peek at respected literature on SEO and status codes if you're in doubt or you'd just like to learn.
Further Reading
If you're curious about status codes and what to do in certain situations, referencing popular frameworks is a good start. Take a look at what they're doing and consider how it may apply to your situation. Learning from others is key to growing your knowledge.
The responders gem (which was extracted and removed from Rails 5.0) has a suite of tests illustrating how you could use status codes in your application.
In this test, you can see 201 Created
is expected when the request results in a resource being created.
def test_using_resource_for_post_with_xml_yields_created_on_success
with_test_route_set do
@request.accept = "application/xml"
post :using_resource
assert_equal "application/xml", @response.media_type
assert_equal 201, @response.status
assert_equal "<name>david</name>", @response.body
assert_equal "http://www.example.com/customers/13", @response.location
end
end
To handle an error, the test expects 422 Unprocessable Entity
. It might be a good idea to use this status code when the user encounters a validation error.
def test_using_resource_for_post_with_xml_yields_unprocessable_entity_on_failure
with_test_route_set do
@request.accept = "application/xml"
errors = { name: :invalid }
Customer.any_instance.stubs(:errors).returns(errors)
post :using_resource
assert_equal "application/xml", @response.media_type
assert_equal 422, @response.status
assert_equal errors.to_xml, @response.body
assert_nil @response.location
end
end
Reference Material
- Wikipedia: List of HTTP status codes: A raw list of standard status codes.
- Mozilla: HTTP response status codes: Explains the common use cases for each status code.
What's your experience with status codes? Have you been a part of any interesting debugging sessions with status codes?
Top comments (0)