Rest API best(?) practices reloaded
The last one and a half year I’m involved in 2-3 projects that expose a big set of Rest APIs for “external” use. I will come back later and explain why the word external is insides quotes. During this period we had to design, re-design and re-structure some times these APIs. This blog post expresses my personal thoughts about some best(?) practices that will help you when dealing with Rest API.
Design well, design early
Implementing Rest services is a very trivial task for many languages. This means that, no matter which underlying framework you choose, with some minimal configuration and a few lines of code you can have in less than an hour a running Rest service. While this is really convenient, especially for inexperienced people, it can lead you very quickly to a low-quality API design. So before you start writing some code, step back for a minute (or more) and think. Try to design your API. Spend enough time to understand the business domain and figure out what API clients need from your system. For instance, if your system is a database for coin collectors, decide if you want to allow clients to add new coins or just fetch existing data. What kind of queries are needed? How will you handle requests that will retrieve a large amount of data? Answering this questions quite early will help you develop a more realistic API.
Names and Methods
There is a lot of discussion out there about resources naming and how they should be organized. Again based on my experience I suggest three, simple to follow, rules.
- Use ONLY nouns. For instance if you want to provide a service for searching the coins database avoid naming the endpoint like this: /searchCoins or /findCoins or /getAllCoins etc. A simple /coins resource is enough so when clients send a GET request that resource they will expect to get a list of all available coins. Similarly if you want to add a new coin to the database avoid using names like : /addCoin or /saveCoin or /insertCointToDatabase . You can just use the resource you already have for fetching coins and instead of a GET request, just send a POST request or a PUT request for updating a coin.
- What about getting a single coin. The suggested best practice is to use an endpoint providing a resource parameter so for instance if clients want to get the coin with id 20 then a GET request to /coins/20 should be enough. Let’s see another more complex case. Let’s say that that you want to allow clients to add images for each coin. A quick but dirty solution would be : /addCoinImage or /addNewImageToCoin etc. A slightly better approach would look like /coins/addImage but like I said just throw away any verbs. So we can step on the resource we provided for getting data of a specific coin and allow clients to add an image by sending a POST request to this endpoint : /coins/20/images. So far so good. But every rose has its thorn. Let’s assume that we want to allow some super-users to delete coins from the system. Based on what we discussed until now a simple DELETE call to the /coins/{id} resource would be enough. But can you imagine how problematic would be that approach if the {id} is just a sequence number of the table “COINS” ? Someone could start sending DELETE requests to that resource by increasing the id number each time causing our system to eventually lose all data. My point is that using identifiers as resource parameters is fine as long as these identifiers are impossible or too hard to be guessed. So if you identify an entity with a serial number just forget this implementation. What I suggest is, instead of passing the coin identifier as a resource parameter, send a DELETE request to the /coins resource and include in the request body (let’s say as a json document) enough parameters to uniquely identify the entity object you want to delete.
- Use domain specific names as much as possible. If your domain has an entity of Coin Collectors then just use the word collectors instead of users or accounts when designing the API. Try to avoid too generic names that don’t mean anything or are controversial to the clients. Follow the same principles for query parameters as well. It’s also strongly advised to keep the names of query parameters as short as they get, but still meaningful. For instance if you want to query coins that was issued in a specific year a very nice choice for the query param could be : issueYear where as the following names are too large or might confuse clients: year (not clear), yearOfFirstIssue (useless extra information)
Error handling and Responses
When dealing with errors and responses my experience shown that it’s much easier for clients to expect the same json response structure every time they send a request, no matter if the outcome is successful or not. For instance let’s say that you want to add a new coin sending a POST request to the resource /coins. A successful response might contain the following json document:
{ "meta":{ "code":200 }, "data":{ "coinId":"a7sad-123kk-223" } }
and an error response something like this :
{ "meta":{ "code":60001, "error":"Can not add coin", "info":"Missing one ore more required fields" }, "data":{ } }
Notice that for both possible outcomes (error, success), the json response document has the same basic structure. There are two basic elements:meta and data. The former contains information about the result and in case of error includes a specific error code, a message (“error”) describing what went wrong and a more detailed explanation (“cause”) of the root cause of the failure. The latter (optionally) contains any data returned from the service. For instance in the example above, when adding a new coin is successful the service returns back the unique auto-generated identifier for that coin. In case of error, typically this element should be empty. The gain of this approach is that clients always expect a standard return for all services of the same API. Moreover we can pass additional information when something is not completed successfully. Clients can use (at least) the error attribute to show it as a message to the end user and use the info attribute for logging purposes. Alternatively they can handle the responses based on the error code as long as they know the meaning of each number. Please bear in mind that these error codes are not http statuses. You should still return the correct http status upon each request (i.e. 400 , 401 etc.)
Before we discuss the subtopic of documentation I’d like to point out another important thing you need to have in mind. Assume that we don’t allow the deletion of coins coins but a client tries to send a DELETE request at the /coins/{id} resource. Normally it will get back a 405 http error from the web container. I find very useful to filter all these responses and return again the same json document. So in the case of the above error we could return:
{ "meta":{ "code":405, "error":"Method not allowed for the /coins/{id} resource", "info":"Method DELETE is not allowed for that resource. Available methods : GET, POST, OPTIONS" }, "data":{ } }
Isn’t a lot better? The response contains the same information ( 405 http error status ) but additionally informs the client the available methods for that resource.
Documentation
Last but not least. Spend some time to provide a professional and developer friendly documentation and make sure that is always updated. An outdated documentation is far worse than no documentation at all. You can use one of the several open source and free tools that exist for that purpose and well-document all your APIs. Even better, provide examples of using each resource and give the expected outcomes in case of an error or a success. Don’t forget, finally to document each error code and provide adequate information so that clients know when errors occur. Several clients ignore any response messages and prefer to provide their own messages based on the error code.
I still have a couple of more practical advice to write down, especially about API versioning and security but I think both of them can fit to a different blog post!
Reference: | Rest API best(?) practices reloaded from our JCG partner Patroklos Papapetrou at the Only Software matters blog. |
Good article. It’s often easier to go the lazy way when you write REST…. this is a great reminder of how to do it right.
Thanks a lot for the kind words
I start to learn REST design/implementation, it’s a very useful article.