RESTful APIs

10 DOs and DON’Ts

Julien Garrigues
Creative by Black Pug Studio
8 min readMay 16, 2018

--

In a world where RESTful APIs are used every day to feed our lives with more data than ever possible, it is necessary to understand how they work, or at least how they SHOULD work.

“Most APIs have unclear, incomplete, or simply missing documentation!”

As a developer, I was shocked to discover how few APIs actually follow the basic rules and guidelines in place. So, I thought I would show a few general DOs and DON’Ts based on live APIs that I’ve encountered or used in the past (without mentioning where they were found, no need for finger pointing of course!).

I won’t go into extensive details about everything. In fact, I’ll try to keep it short and straight to the point, and if you are interested after reading my thoughts and sparked curiosity, I would invite you to dig a bit further.

Dos & Don’ts of RESTful APIs (illustration by Mackinley Raftery)

1 — Let’s just use POST for everything!

“You don’t have to create a new endpoint for each action related to a resource.”

👎 DON’T

POST /api/v2/CreateOrder
POST /api/v2/GetOrder?id=12345
POST /api/v2/GetOrders
POST /api/v2/DeleteOrder?id=12345

👍 DO

POST /api/v2/orders
GET /api/v2/orders
GET /api/v2/orders/12345
DELETE /api/v2/orders/12345

I have come across this example a couple of times and in rather large APIs related to online retail or banking. This case is wrong for many reasons.

Firstly, you don’t have to create a new endpoint for each action related to a resource. Instead, you should have a single endpoint (in this case /orders), and use the METHOD sent with your request to determine which action should be performed by the targeted endpoint.

Secondly, the target resource’s ID should not be passed in the query string but should be a sub-path or part of the URL instead (/api/v2/orders/12345). This is the common structure of a RESTful API and we can also imagine that this resource could potentially contain extra ramifications (/api/v2/orders/12345/products).

2 — While we’re at it…

👎 DON’T

Update a post ->
POST /api/v2/posts/12345

👍 DO

Update a post ->
PUT /api/v2/posts/12345
PATCH /api/v2/posts/12345

You will find this example in quite a few APIs out there.

If you read the documentation on HTTP Methods and how they are supposed to be used, you will find that POST is used to create a resource, but to update that same resource you are supposed to use PUT or PATCH.

3 — Let’s use PUT… What do you mean “404”?!

The PUT request should not return “404"…

👎 DON’T

Post 12345 does not exist.Request ->
PUT /api/v2/posts/12345
Response Status ->
404 Not Found

👍 DO

Post 12345 does not exist.Request ->
PUT /api/v2/posts/12345
Response Status ->
201 Created

The PUT request should return “201"…

👎 DON’T

Post 12345 does not exist.Request ->
PUT /api/v2/posts/12345
Response Status ->
200 OK

👍 DO

Post 12345 does not exist.Request ->
PUT /api/v2/posts/12345
Response Status ->
201 Created

The use of PUT is often misunderstood.

In the first case, a request using PUT is supposed to result in the creation of a resource (201 Created) if it does not exist or 200 OK if it already exists, therefore it should never result in a 404 Not Found.

Furthermore, the second case created the resource but sent back a status 200, which would have been “OK” (see the pun here?) if the resource was updated, though in our case the request resulted in the creation of the resource and should return status 201 Created.

This last part is extremely important since it is the only way to make the difference between the creation or the update of a resource using PUT.

4 — Everything is “OK”

POST requests should not return 200…

👎 DON’T

Request ->
POST /api/v2/posts
Response Status ->
200 OK

👍 DO

Request ->
POST /api/v2/posts
Response Status ->
201 Created

Responses with an empty body should return 204…

👎 DON’T

Request ->
DELETE /api/v2/posts/12345
Response Status ->
200 OK
Response Body ->
NULL

👍 DO

Request ->
DELETE /api/v2/posts/12345
Response Status ->
204 No Content
Response Body ->
NULL

In the first scenario, the correct response to a successful POST request is always 201 Created. 200 OK is used for updates or deletions (including a body).

In the second scenario, if the request was successful and the response’s body is empty (or null) you have to return 204 No content. This is not only to be picky, but it is used to prevent front-end errors. In most cases, if the status returned by a request is 204, the parser will not be called even though the request expected JSON content.

In my opinion, you should never return an empty body. It is considered “Bad practice” in some specification documents. If a resource was deleted, you should send back the resource, and possibly append the response with the exact time of deletion and/or a “deleted” flag.

5 — Server: “400 Bad Request. Body: Empty…” — Client: “Thanks…”

“Yeah… that happens.”

👎 DON’T

Request ->
POST /api/v2/orders
Response Status ->
400 Bad Request
Response Body ->
NULL

👍 DO

Request ->
POST /api/v2/orders
Response Status ->
400 Bad Request
Response Body ->
Content-Type JSON
Body
{
“code”: “ERR0001”,
“message”: “Your cart is empty.”
}

It is common sense to return an error message when sending back a status 400. It will help the end-user or the API consumer to understand why the request failed since this status is not as self-explanatory as “404 Not Found”.

Two parts are critical in a good error response: a unique reference (or code) for the error and a description (or message) in plain text. The description should give the user enough information to understand where the error is coming from and what needs to be provided or changed.

6 — “400 Bad Request. Body: Some random error” — Client: “But… this is a GET request”

👎 DON’T

Request ->
GET /api/v2/stores
Response Status ->
400 Bad Request

👍 DO

Request ->
POST /api/v2/stores
Response Status ->
503 Service Unavailable

So, I was using a rather large eCommerce API, and it so happens that they were using a third-party geolocation service. As you can imagine, the third-party service went down, and I was left with an error 400 on a GET request. My advice is, if you’re using a third-party service and it goes down, to return a more useful and accurate status such as 503 Service Unavailable.

Note: Do not return a 4XX error status if the error comes from the server — it is highly confusing and will drive your users completely insane!

7 — 404 (Not Found) vs 410 (Gone)

👎 DON’T

Request ->
DELETE /api/v2/posts/12345
Response Status ->
200 OK
Request ->
GET /api/v2/posts/12345
Response Status ->
404 Not Found

👍 DO

Request ->
DELETE /api/v2/posts/12345
Response Status ->
200 OK
Request ->
GET /api/v2/posts/12345
Response Status ->
410 Gone

So, this example depends on whether or not you keep track of what has been deleted. But if you do, and a GET request is made on a deleted resource, you should return status 410 Gone. It is useful for your users to know if the resource they’re trying to access once existed or not.

Just imagine for a second:

I am given a link that returns error 404, I immediately think that it is a mistake made by the person who gave me that link but in fact the resource existed and was deleted. If I were given proper feedback in this scenario, the situation would have been clearer and less confusing for both me, and the person who supplied the link.

8 — Get your priorities right

“You should always authenticate the user.”

👎 DON’T

Request ->
PUT /api/v2/posts/12345
Response Status ->
400 Bad Request
Request ->
PUT /api/v2/posts/12345
Response Status ->
401 Unauthorized

👍 DO

Request ->
PUT /api/v2/posts/12345
Response Status ->
401 Unauthorized

You should always authenticate the user. Typically, your users should go through your authentication gate first, to check the Authorization header for their credentials and validate them or return a status 401 Unauthorized. It is then that the request is validated, or not and, in that case, returns 400 Bad request.

Note: 403 (Forbidden) can be returned at the very end. It means that the User was successfully authenticated, and the request validated, but this particular user does not have the permission to perform this request. To summarize quickly: 401 > 400 > 403.

9 — “We want our API to be public, completely open, and free to use!” — The Care Bears

“You want to open your service to the world for free, but even an open API shouldn’t stay unprotected, or vulnerable to attacks or abuse.”

👎 DON’T

Request 1 ->
GET /api/v2/posts
Response Status ->
200 OK
Request 999,999 ->
GET /api/v2/posts
Response Status ->
504 Gateway Timeout

👍 DO

Request 1 ->
GET /api/v2/posts
Response Status ->
200 OK
Request 51 ->
GET /api/v2/posts
Response Status ->
429 Too many requests

I appreciate that you want to open your service to the world for free, but even an open API shouldn’t stay unprotected, or vulnerable to attacks or abuse. You must put some restrictions in place to avoid and protect against DDoS (Distributed Denial of Service) attacks. One possibility is to limit the number of requests made based on the user’s IP address, but I would instead recommend using some kind of ID-based Authorization system. With this system, the user gets a client ID once registered and must use this client ID in order to consume the API. It allows you to keep record of your users’ requests and put some restrictions in place.

Note: Remember to use status 429 Too many requests in the eventuality that one of your users goes over the authorized limit.

10 — “500 Internal Server Error” more like “500 Whoops, code broke”

“Remember: There is no excuse for a status 500 Internal Server Error, simply because it means there’s an unhandled error in your code.”

👎 DON’T

Request ->
PUT /api/v2/posts/12345
Response Status ->
500 Internal Server Error

👍 DO

GOOD CODE

If you have to return a 500, it should be because you caught the error and it is an exception that you did not think about but at least it is handled, and you are aware of its existence.

There is always a lot more we could talk about here, but I think that we have covered some of the most common issues I’ve regularly encountered. If you want to highlight something, ask questions, or give your own opinion, feel free to leave a comment and I will answer with delightful pleasure!

You will most likely run into the same issues I have highlighted above, but the most common is the lack of documentation.

Most APIs have unclear, incomplete, or simply missing documentation! So, take a deep breath and get ready to explore the dark world of blind debugging!

A little treat for developers, if you have any doubts here is a very good reference: https://stripe.com/docs/api.

--

--

Julien Garrigues
Creative by Black Pug Studio

Director of Technology @BlackPugStudio | Organizer @GDGgalway | Developer | Gamer | Guitar player | Art lover