What is good RESTful API and what are consequences of designing bad API?
Nowadays more and more service suggest their APIs, we as web/backend developers usually provide api to our frontend applications or to other backend services. In this situation we are highly dependent on the quality of API and not only in terms of understanding, but usability in general.
As consumers of the API we have to deal with such problems as:
- not clear API with bad documentation
- duplicated or looks like duplicated endpoints
- misleading methods of the method
- misleading status codes
- not straight forward behaviour of the endpoints
- performance heavy calls
- redundant/not enough information in the payload
- exposing internal mechanics and business logic
On the other hand well built API:
- is self-described with methods and paths
- has just sufficient amount of data in the response/request payload
- has as simple and straightforward as possible behaviour of the endpoints
- has easily understandable signature of endpoints
- has endpoints that complement each other without overlapping
- hides internal functionality
A typical consequence of the having and using bad API is not needed complexity of dependent code: when we need to do several calls to complete one simple action or implement filtering/caching functionality to overcome problems of the consumed API.
Ideas worth to follow
Most of the problems above can be leveled by simply following some practices and approaches that just help to make your contract cleaner and more predictable.
Plural names
It is better to use plural names in your endpoints related to domain model:
The idea behind this rule is that you know that you deal with collection of entities instead of
one entity. Singular duck
would lead to confusion: is it one default duck or it is collection
of ducks.
Nouns without verbs
Use HTTP methods meaningfully - each method is created for it’s own purpose, this purpose is
described with a verb like get
, post
, delete
and others. These verbs in addition to the
nouns in path can describe well the intended action.
So instead of having:
we can have something like:
In concise manner we can clearly describe intention without need to specify additional path for common operation.
You should be able to read the signature of this endpoint as:
- WHAT is meant to be done with your domain model -
GET
,POST
,DELETE
etc. - WHICH domain part will be affected -
/users
,/ducks
- plural nouns - WHAT are the concrete details of the request -
path variables
,query parameters
,body
or combination of them
Filtering
Use query parameters to filter results of the request.
With parameters you queries will become much cleaner and help you to combine several filters at once. So you still have one endpoint, but behaviour of it is controlled using parameters.
Pagination
This rule is related to the response payload. It is a good practice to paginate results of the
request. Sometimes we even have no other choice because the set of requested data might be really
huge. So use offset
, limit
, page
parameters to limit the output of the request.
Content type
It is wise to specify the content type of the request/response of your API and implement according restrictions on the backend (it will help to improve security aspect)
Corresponding status codes
It is obvious, but it is worth to mention again: we have to use different status codes that describe returned result. So just check the list of possible statuses to find the best match.
NOTE: Be aware that sometimes this approach can lead to security vulnerabilities like
enumeration
. For example: you have just created user and this request returned 201 Created
, the
next call then returns 400 Bad Request
because this user already exists. In this case you can
potentially enumerate users and conduct an attack.
Documentation
Right now it is good practice to have your API described as a document. There are several services that can help you with that:
They helps you to create nicelooking page which has the list of endpoints with the description and all the details needed to successfully use the API. There are several approaches:
code first
when you generate documentation based on the implementationcontract first
when you create documentation first and only then start implementation
Contract first
approach can help you to define common ground with all the teams beforehand and
to do this all you need to do is to create yaml
contract definition which will be converted
into html page. This page might look like this:
Advantages:
- it is pretty handy and easy to use
- contract definition can be checked against real contract to see whether it is the same or not (of course it is only shallow check)
- specification can be converted to postman collection
- dummy response from the hosted contract can be used to start implementation
Disadvantages:
- we need to make efforts to keep contract up to date (there is a swagger library that helps you to define contract using annotations)
- there is some learning curve
- sometimes we have to deal with the gap between real implementation and defined contract
Explicitly define error object
Error or exceptions are required part of any application and contract as well. We have to be really specific of what can go wrong and what type of object you will receive as a response in this case. The list of such exceptions has to be succinct and sufficient to fulfill your needs. Be aware to not create a lot of different classes for exceptions because:
- it might be tricky to handle all of them
- you might expose to much internal information
- to specific exceptions can strengthen coupling between systems
Idempotency and safe methods
Keep in mind standard behaviour of the methods and stay inline with it:
GET
,OPTIONS
,TRACE
,HEAD
are idempotent and safe methods which means they do not change the state on the server.PUT
andDELETE
are idempotent methodsPOST
is not safe and is not idempotent
Additional info about idempotency
NOTE: Keep in mind the main rule not to mix methods with wrong functionality:
GET
/ducks/123/delete is totally wrong.
Exceptions
Sometimes we have to obey other rules like specific path that will be used by some third party service or we have to do something out of the standard behaviour - try to minimize this cases and make implementation as clear and concise as possible.
Conclusions
There are not a lot of rules that we have to follow if we want to have neat and pleasant to use RESTful API. Let’s make our world a bit better by creating new APIs and refactoring current API’s with this rules in mind.
Thank you and as always I hope it was useful for you.