Best Practices for your REST API
- By Preneesh AV --
- 29-Jul-2018 --
- 63 Comments
Developing apps to request information to a REST API either fetch data from server or send them. We might have to integrate with APIs we have developed and other third parties we had to integrate with, but I have seen a list of errors everybody should avoid developing a REST API.
Avoid returning always the same HTTP status code
Some REST APIs that always returned a 200 status code. As no errors were returned in this API, you could think everything was perfect, right? But it was not, it was a mess indeed. How can you differ successful responses with error responses coming from client or server? Regarding body? This logic should be in backend, never in client side application or mobile app. It should not contain business logic about server API. If server is changed, it may be causing errors in client if it is not updated at the same time.
Use HTTP status codes for server responses. You can find the following status codes according to RFC7321
- 1xx Informational response
- 100 Continue
- 101 Switching protocols
- 102 Processing
- 2xx Successful response
- 200 OK
- 201 Created
- 202 Accepted
- 204 No content
- etc.
- 3xx Redirection
- 300 Multiple choices
- 301 Moved permanently
- 302 Found
- 304 Not modified
- 305 Use proxy
- etc
- 4xx Error in client
- 400 Bad request
- 401 Unauthorized
- 403 forbidden
- 404 Not found
- 410 Gone
- etc.
- 5xx Error in server
- 500 Internal server error
- 501 Not implemented
- 502 Bad gateway
- 503 Service unavailable
- etc
Dont use different error response formats
By keeping the same error format, you’ll simplify clients to integrate with your API. For example, you can define a structure as following and has any middleware in your server detecting error responses and wrapping error in a response such as
Response status code: 403
Response body {
code: 5,
message: "Error message"
}Furthermore, servers must not return error stack-trace, they should be handled.If resonse is handled as json output then the same to be rendered for all kind of errors.
Differ HTTP methods
Their is common practice to develop REST APIs with a single HTTP method, even coding some protected web services by authentication. I have found 2 cases, when all requests implement GET method sending session information as query param or when all requests implement POST method with session data information included in the body. They are not well designed, we have different methods to do different actions.
HTTP defines a number of standardized methods. Let me summarize the most used with HTTP definition:
- GET: Transfer a current representation of the target resource.
- POST: Perform resource-specific processing on the request payload.
- PUT: Replace all current representations of the target resource with the request payload.
- DELETE: Remove all current representations of the target resource.
Suppose we are developing “users” resource, we need to create 5 basic web services of the API.
| HTTP Method | PATH | BODY | Successful Response | |
| Fetch all users | GET | /users | List of user representations | |
| Get a user | GET | /users/<userId> | User representation | |
| Add a user | POST | /users | User representation | User representation |
| Edit a user | PUT | /users/<userId> | User representation | User representation |
| Delete a user | DELETE | /users/<userId> |
As you can see, it’s very clear to know what each resource does.
Versioning your API
Apps in Google Play or AppStore are continuously releasing new versions of their app, but what about server? It is also increasing in functionalities. The problem comes when users don’t update when there is a new update – it frequently happens. One can’t afford having a server per app version, then what can I do? The only solution is to have different versions in your API.
Also, remember when you have different versions of your API you must think in backward compatibility. Don’t abolish anything that it’s currently working after a new release.
Include session token in headers
Most APIs have private requests, requests can only be reached if there is an active session. Sometimes, developers include session token as part of URLs using a query param or implementing all requests with POST method to send session token as a param in the body. It does not make sense to implement “Get all users” web service with a POST method. As you have learnt above, this method should be GET.
Therefore, support HTTP “Authorization” header in your API and send it in all requests with the user credentials to authenticate with server. It can flexible and you can adapt to your authentication protocol easily.
Internationalize API
We do return error, successful or informative messages in our API. The client must not contain any server logic, not even for messages. Sometimes, we need to show those messages in the client. Then, we need to send internationalized according to client device.
Supporting “Accept-Language” header in our API and clients are responsible to send its language in every request using this request header. For example: setting “Accept-Language: es” you should get messages in Spanish.
Recommended to use any i18n library to support this, although you are currently supporting just one language. In the future, you will need to support more than one and it will be a nightmare to replace hard-coded strings by i18n tokens.
Paginate your results
Implement pagination in all web services you think you will return too much data. You will reduce the response time and data sent to clients. If that client is our mobile device, think you might not have a good internet connection, as consequence, as less time and information is returned, it will be better.
Respond what you are requesting
If you are requesting to a resource, return its representation or a list of them; avoid responding with something different. You might think you are doing in the right way, but I will show you a common example when people get wrong.
If we call to the web service to fetch all users, some APIs return a response like the following one:
{ "users":
[
{
"name": "John",
"surname": "Smith",
"age": 25
},
{
"name": "Peter",
"surname": "Potter",
"age": 47
}
]
}Do you think it is OK? No, this response is not correct due to you are getting an object with a list of user representations. You should have gotten just a list of user representations.
[
{]
"name": "John",
"surname": "Smith",
"age": 25
},
{
"name": "Peter",
"surname": "Potter",
"age": 47
}
Return correct data types
Return correct data types and take advantage of null objects in case you don’t have that information.
For example, if we want to return user information. Fields we have from users are name, lastname and age.
{ "name": <string>, "lastname": <string>, "age": <int> }
If age were not mandatory, it could be null. In that case, avoid using age as string and return an empty string (“”), return null when it is unknown.
{ "name": "John", "lastname": "Smith", "age": null}
Test backend with automated tests
Tests should be mandatory in all projects but even more with the part that has all product logic. They will help you not to mess up or including new bugs in features that are currently working. So, next time you will start to develop a new feature in your backend, include some integration and unit tests, you will thank you.
Use HTTPS and valid certficates depending on your clients
For a secure API, firstly we need to run it with a HTTPS scheme. But, be careful when you are choosing the certificate that you want to use in your app. Avoid self-signed certificates or old encryption algorithms. Read encryption algorithms supported by your clients (mobile devices, browsers, etc) and choose one according to your needs.
Make conditional requests
This can be done if we use If-Modified-Since request header. We can cache the last response date we received and ask for changes since that date. This is a very good practices for mobiles devices due to users usually don’t have unlimited data plans.
