On many occasions, we don’t value the importance of establishing a clean, understandable, and scalable architecture when developing a REST API (or attempting to have this pattern), but in the long run, this has a giant impact as the application grows.
Suppose that the time has come to expose the interface that we were developing to users, how sure are you that they understand the same thing that you try to transmit in your interface? Not only is the understanding of the users who consume the application relevant, but also that it is understandable for your team and future people who will come to work with the application. It is essential to establish from the beginning an architecture that everyone will respect.
These are several of the aspects that I consider of most importance to take into consideration:
1. Use HTTP methods to give your endpoints meaning
REST API encourages us to use an HTTP method for each of the application's CRUD actions. Among them, we have the following variety: GET, POST, PUT, DELETE, and PATCH. The name of the endpoint associated with the resource must be accompanied by the HTTP method related to the applied action.
- GET /get_cats
- POST /insert_cats
- PUT /modify_cats
- DELETE /delete_cats
+ GET /cats
+ POST /cats
+ PUT /cats
+ DELETE /cats
2. Status codes must be according to the result of our API.
One of the most important qualities of our application is that the return of our endpoints is related to the corresponding status code. This means that the moment our result is successful or unsuccessful, we can relate in a more descriptive way the message we want to convey.
For example, if we get a status 200, we can immediately know that our result is successful, otherwise, if we get a status 400, the result is failed.
It is important to know the existing status codes and know which case we need to apply each one of them, since it may happen (and it's very common) that the return message is incorrectly associated with some status code, which is extremely harmful for the application since it generates confusion for developers and consumer users of our REST API.
// Bad, we return status code 200 (Success)
// associated with an error object
{
"status": 200,
"error": {...}
}
// Good
{
"status": 200,
"data": [...]
}
3. Filter, sort, and pagination support
Many cases in any application that consumes our API, will want, in some way, to consume fewer resources from our service, either due to performance, a search system, the information is excessive, or as simple as showing something particular from our resources.
Filter, sort, and pagination, in addition to expanding the functionalities of our API, helps us reduce the consumption of resources on our server.
Let's imagine the case of an endpoint that returns millions of results, how would our server react? (He would surely cry and collapse 😜).
-
GET /cats?race=misumisu&age=1
-> Filtering, retrieve all the cats that have the following properties: race is misumisu and the age is 1. -
GET /cats?limit=15&offset=0
-> Pagination, return 15 rows starting with the 0 row. -
GET /cats?sort_by=name&order_by=asc
-> Sort, return rows sorted by name in ascendant.
4. Plural endpoints
One of the daily discussions that I come across regarding various API developments is deciding whether to use singular or plural for the construction of endpoints. In short, we want to maintain a solid consistency in our application, and for this, my recommendation is to build the endpoints in plural.
Resources will not always have a single result, a table can have many results and even if it had only one, and we place it singular, we will not maintain consistency in the format of the name of our routes.
“Consistency is the key to success” 🔑
- GET /cat
- GET /cat/:id
+ GET /cats
+ GET /cats/:id
5. Name endpoints with the name of your resources
Speaking of consistency, if we know that a route is responsible for handling the actions on a resource, it is essential to name it with the name of the resource directly, so when a person consumes our API, they will understand which entities they are working on.
For example, if you are going to return cats, you are not going to call your endpoint /dogs
🐶.
6. Resource hierarchy
What if we want to access a closely linked entity that belongs to a resource?
To show this relationship we have two options:
- Hierarchically append article in our author endpoint
- Query string
Let's take the classic example of "author" and "articles".
GET /authors/betoyanes/articles/create_cat_memes
GET /articles?author=betoyanes&name=create_cat_memes
These ways are valid and I have seen them in many projects. Personally, I consider it cleaner to use a query string than expanding the current path. The more the application scales, we will surely have a greater hierarchy and in turn, the route will expand. Even so, it is according to the criteria of each person, so use the one you prefer the most!
7. Versioning
As we develop, it is inevitable to have a stable and definitive version of our API, without errors, and bulletproof. Let's imagine we deploy our API and several clients are starting to use it, what would happen at some point when you need to add or remove more data from a resource? Possibly you generate a bug on the external services that consume our interface. That is why it is essential to have a versioning mechanism for our application.
There are several ways, but I am a fan of the versioned URI, in which we will explicitly have the version of our route in our endpoint.
// URI versioning v[x] syntax
GET /v1/cats
GET /v2/dogs
8. Caching
One of the powerful tools that will enhance an API in speed and low resource consumption is caching, the idea is not to ask your database for the same request multiple times if it continues to have the same result. There are several services that can help us implement this system, among them, one of my favorites is Redis.
We have surely heard that implementing a cached functionality generally comes with costs, and this is not the exception. Let us ask the following questions, is the information dynamic or is it static? If it’s dynamic, how often does the information change?
🚨 It is important to be aware of having long periods of information in cache, this can cause false results of the API by keeping the information for a long time, it is recommended to have short periods of cache.
9. Documentation
One of our best weapons and most hated by many people is documentation. In this context, a documented API is essential so that our users who consume it can understand several important aspects of our interface, including accessibility, responses, requests, examples.
Accessibility: The location and access of the interface are one of the most important qualities, and we do not want to give a
how_to_use.txt
to customers. Exposing our documentation on the cloud where everyone can see it, is the most convenient thing we can do.Responses and Requests: The information we provide has to contemplate all the possible results that any resource may produce and how to consume them.
Examples: It is very important to provide examples of how we can consume our interface, even if it is a bash script that we can execute in the console and get a response from it..
Conclusion
Remember that our API is the interface that we expose to consume our back end service, with this in mind, it is important to apply the best possible principles so that the people who consume and work on it, are to their liking.
Although we are developing a personal project, we need to try applying the best principles that we consider, so we can get practice for when we enter a development team or project 💪.
I hope this article has helped you and adds some principles to your collection of good practices for your REST API! Do you have any other principles to add? Let us know in the comments section! 💬
Follow me on LinkedIn or Twitter to up-to-date with my publications 🚀.
Top comments (2)
"GET /cats?limit=15&offset=0 -> Sort, return rows sorted by name in ascendant."
query looks wrong (not related to sorting), or am I missing something?
Hey! Thanks for the catch, I just submitted the correct query <3