Creating a new API is not easy. The important thing is to set rules and follow them. I will show some ideas about the API in the article.
Who is going to use our API?
If we create a new API we must know, who is going to use our API. Each type of API has different requirements.
- WEB API – is used mostly by javascript (typescript…) from frontend application. The API may follow some frontend requirements. Changing API is safe, we do not care about backward compatibility.
- Public API – is used by 3d party systems. We must be very careful. If we put some functionality there, we cannot remove it later, because other systems may use it forever. Backward compatibility is critical.
- Mobile API – it is between WEB API and Public API. Before changing the API we must force our users to upgrade their mobile applications. Sometimes it is not possible.
REST API vs RPC API
I believe the classic REST API is not suitable for the most use cases. HTTP is a protocol designed for browsing HTML pages, not for calling remote methods. I prefer to use RPC (remote procedure call) based on HTTP. It is similar to REST API but technically it is not REST API. The RPC API rules are simple:
- Always use POST methods.
- Input is always JSON, the output is always JSON.
- If the method finishes with a success, the status is 200.
- It the method finishes with an error, the status is 400 (client error) or 500 (server error).
- If the method finishes with an error, always include an error message and application error code in the JSON result.
Why always use POST methods instead of GET?
GET query string is more complicated. We have to be careful about sensitive data as the query string usually goes to the server log files. We have to be careful with proper character encoding. And length of query string may be limited.
Compare this query string with GET method:
/search-result?fulltextFilter=%C5%BDlut%C3%BD%20k%C5%AF%C5%88&sortBy1=createDT_DESC&sortBy2=state_ASC&limit=10
And the same with JSON POST method:
{ "fulltextFilter":"Žlutý kůň", "sort": [ {"column":"createDT","type":"DESC"}, {"column":"status","type":"ASC"} ], "limit": 100 }
Why having a different parameter encoding for GET and POST methods?
The best way is to have it united. Everything is POST and it is simple and clear.
Sometimes GET method is useful. For example, if we need to create a permanent web link to the document. But it is not RPC in this case.
Error handling
Why not use other error codes than 400 or 500?
Because HTTP status codes are related to the HTTP protocol, not the application itself. For example, error code 404 – the resource is not available. We want to distinguish between two cases:
- URL address of API is invalid.
- The requested document does not exist.
We should not use the same code 404 for both cases. It may be confusing. For the first case, 404 error should be used, because it is an infrastructure problem. This error is generated by a web container or proxy.
For the second case, 400 general client error is better. This error is raised by our application and it is not an infrastructure problem. Infrastructure guys are not confused. The error message with 400 status code may look like:
{ errorId: "780f959c-aaff-11ea-bb37-0242ac130002", localizedUserMessage: "Požadovaný dokument neexistuje", systemMessage: "The requested document does not exist", errorCode: "DOCUMENT_NOT_EXIST" }
- errorId – Unique identification of the request. With this ID we can investigate log files and find important information.
- localizedMessage – This error message may be shown to the user.
- systemMessage – This message is important for the software developer who is using our API.
- errorCode – unique application error code.
400 vs 500
- 400 – The client error. The client sends us invalid parameters or requests something that we cannot process. In this case, we return error message in JSON file.
- 500 – The server error. It is our problem, typically RuntimeException or similar. In this case, we return JSON with a general error message and errorId. Then we put as much information possible to the server log (errorId, parameters, stack trace, userId …)
Paging
It is not a good idea to return a lot of data in one response. Paging comes into play. The client requests the data, we return a limited set and the client must traverse the result and ask for each page in the different requests.
And we must know who is calling us and why. Because we can offer stable or unstable paging.
What is stable paging?
Stable paging guarantees we traverse all the rows. Each row will be there just once. This is important when some record is created or deleted during the traversal.
Stable paging is ideal for public API when 3d party clients are connected and grabbing data for processing. If we want to implement stable paging, we have to usually enforce database sorting. Sorting by the creation date is a typical example.
Unstable paging is suitable for front-end applications. Users usually do not care if some row is missing in the result set on the web page. The implementation is easy. In this case, clients can define their own sorting.