Awesome HTTP Semantics You Should Know
Software engineers love to debate semantics in their code. A discussion on what would be the best name for a function or variable can easily lead to dozens of comments in a code review. And yet, for some odd reason, many engineers are not as passionate about HTTP semantics such as the different HTTP verbs and status codes.
This is a small piece about some awesome HTTP semantics that I think are overlooked or forgotten in most projects. Whether or not they actually bring any major value is debatable. Some suggestions here might feel like an "artisanal" thing so use your own judgment to see which of these are worth a refactor. But once you learn the semantics correctly, they won’t require much extra worth to implement correctly in a new endpoint or project.
Don’t Just Use POST
There are so many examples of incorrect usage of the POST
method. Here are some examples:
- Updating or creating a resource in an idempotent way should be done via
PUT
method. WithPUT
methods we promise a certain behaviour: the target resource will be created or replaced with the state defined in the request message content. There will be no other side effects and executing the request multiple times will always lead to the same result. (But it could lead to a different response, more on that later!) - Deleting resources should be done via the aptly named
DELETE
method. - For endpoints that let us update parts of the state, but are not idempotent or come with side-effects, we should use the
PATCH
method.
200 Is Not the Only Success Status
Returning 200 OK
for every successful response is, well, ok! But there are some better semantics you could use.
For example, the 201 Created
code is a pretty neat specifier to use when responding to a create request. This is also very useful if you are using PUT
to allow both create/update requests because it allows you to distinguish which of the two operations was performed. For example:
- The first call to
PUT /item/my-cool-item
will result in201 Created
. - Follow-up
PUT
request to update the item will return a200 OK
.
The other interesting success code is 202 Accepted
which indicates that the request has been accepted for processing, but the processing has not been completed. It’s a noncommittal response that will require the client to follow up and monitor the status. It’s useful when dealing with APIs that provide wrapper functions, for example:
- You request an async job run and get the response
202 Accepted { id: 12345 }
- You then query
GET /job-status/12345
to check on the status.
Client Errors for Resource Updates
Suppose two users, Alice and Bob, both load a resource with an id 12345
and version 1
and they want to update it. Alice submits her request first and it gets accepted. Then Bob submits his and gets rejected because he’s trying to submit a version 2
that already exists.
What would be the correct response code? Semantically, 400 Bad Request
is not a complete description of what happened. A better and more explicit code would be 409 Conflict
.
But we can go a step further. If you are using entity tags (ETags), then you get two powerful new semantic statuses. Before I explain these, a quick primer on the idea of an entity tag if you are unfamiliar with these.
Entity tags are lightweight headers that provide an identifier for a specific version of a resource. The tag is usually a hash.
In our example from earlier, this means that when Alice and Bob load the same resource it would also with the same ETag
value, e.g. 23ccbc9e-eacb-11ed-a05b-0242ac120003
. After Alice edited the resource, the ETag of the resource will change since it is now a new version. Let’s say this is a new value such as 5f851db5-3c9a-44e4-ae8e-2a993c73dd4d
.
When Bob sends his request to update the resource, the client will include the previous ETag
value in an If-Match
header. The server will first validate if this matches the existing resource. Since it does not, the server doesn’t have to waste any more time on parsing or validating the request. It can simply reject it right away with the status code 412 Precondition Failed
.
But what if Bob’s client is out of date or buggy and doesn’t include the If-Match
header? Then you can use the 428 Precondition Required
response.
Is It Not Found or Is It Gone?
A 404 Not Found
error makes it clear that the requested resource was not found. But that still leaves two other questions:
- Could this be temporary? If I retry in a few hours will it be back?
- Did this ever exist in the past? Was it deleted?
We can’t really provide much of an answer to #1, but if you are sure about #2 and want to provide an explicit answer, then the status code 410 Gone
is a good choice.
An example where this might be useful is when you allow users to delete their account but want to preserve the notion that they were once registered and prevent others from claiming their username.
Hiding Project Deadlines within Not Implemented
To close off, here’s something that is not useful but shows a cool and fun semantic. Let’s say you created an API endpoint at POST /my-cool-feature
, but there’s no handler code to actually process the request.
You can return the response 501 Not Implemented
to anyone who calls it. But you can have a bit more fun. If you expect the endpoint to be implemented at a specific date, you can also return a Retry-After
header with your launch date!
Comments ()