BEST practices for REST API design.
In this article, we will look at how to design REST APIs to be easily understandable.
What is REST?
REST is an acronym for REpresentational State Transfer. It is an architectural style for distributed hypermedia systems and was first presented by Roy Fielding in 2000.
REST APIs are one of the most commonly used web services in the modern world. They allow browsers, mobile apps to communicate with the server.
That’s why the proper design of REST APIs is important. Proper design help to scale, manage our backend services. Otherwise, we create problems for clients that use our APIs.
We will discuss some common design examples. We will be using the NodeJS and ExpressJS framework for this tutorial.
Accept and respond with JSON
REST API should accept the JSON request from a client and send a response back to the client in JSON. JSON is standard for transferring data. JavaScript has inbuild methods for manipulating JSON data easily.
There are other ways to transfer data. XML isn’t widely supported by frameworks without transforming the data ourselves to something that can be used, and that’s usually JSON.
Form data is good for sending data, especially for sending files. But text and numbers, we don’t need form data. Most of the framework support for sending data directly.
In our REST API response, we should set Content-Type
in the response header to application/json
after the request is made. HTTP clients like mobile app parse the response according to the content type.
We should also make sure that our endpoints return JSON as a response. Many server-side frameworks have this as a built-in feature.
Take look at an example:
In the Express framework, we use body-parser
middleware for parsing JSON request body and then we can call res.json()
method with the object that we want to send to the client.
bodyParser.json()
parses the JSON request body string into a JavaScript object and then assigns it to the req.body
object.
Set the Content-Type
header in the response to application/json; charset=utf-8
without any changes. The method above applies to most other back end frameworks.
Use nouns for endpoints
We should use nouns for endpoints because nouns represent entities. Using these endpoints we retrieving or manipulating the data.
In our code HTTP request methods already has verbs. For instance, some like ‘get’ and some like ‘retrieve’, so it’s just better to let the HTTP GET verb to tell us what an endpoint does.
The action should be indicated by the HTTP request method that we’re making. The most common methods include GET, POST, PUT, and DELETE.
The verbs map to the CRUD operations.
We should name collections with plural nouns. It’s not often that we only want to get a single item, so we should be consistent with our naming, we should use plural nouns.
Nesting resources for hierarchical objects
The path of endpoints deal with nested resources should be done by appending the nested resources as the name of the path that comes after the parent resource.
Suppose if we want to retrieve comments for a particular article, we should append /comment
path to the end of the /articles
path.
In the above example, we can use the GET method to retrieve all comments on path /articles/:articleId/comments
.
Here comments
are the children objects of the articles
.
Handling error
We should handle errors properly and return proper HTTP response code to end clients about the error.
Common error HTTP status codes include:
- 400 Bad Request — This means that client-side input fails validation.
- 401 Unauthorized — This means the user isn’t not authorized to access a resource. It usually returns when the user isn’t authenticated.
- 403 Forbidden — This means the user is authenticated, but it’s not allowed to access a resource.
- 404 Not Found — This indicates that a resource is not found.
- 500 Internal server error — This is a generic server error. It probably shouldn’t be thrown explicitly.
- 502 Bad Gateway — This indicates an invalid response from an upstream server.
- 503 Service Unavailable — This indicates that something unexpected happened on the server-side (It can be anything like server overload, some parts of the system failed, etc.).
In the code above, we have a list of existing users in the users
array with the given email.
Then if we try to submit the payload with the email
value that already exists in users
, we’ll get a 400 response status code with a 'User already exists'
message to let users know that the user already exists. With that information, the user can correct the action by changing the email to something that doesn’t exist.
Allow filtering, sorting, and paging.
The database behind the frontend may huge. Sometimes there is so much data, it should not be returned all at a time because it will bring down the system. That’s why we need filtering.
In the above code, we have req.query
variable to get the query parameters.
As per the query request, we filter the results and response send back to the client.
Request:
/employees?lastName=Smith&age=30
Response:
[
{
"firstName": "John",
"lastName": "Smith",
"age": 30
}
]
Note: For instance, we may want to extract the query string from a URL like:
http://example.com/articles?sort=+author,-datepublished
Where +
means ascending and -
means descending. So we sort by author’s name in alphabetical order and datepublished
from most recent to least recent.
Cache data
We can improve the performance of API, caching the response into the main memory instead of querying into the database. It will improve performance. Response delivered faster.
There are many kinds of caching solutions like Redis, in-memory caching, and more. We can change the way data is cached as our needs change.
For instance, Express has the apicache
middleware to add caching to our app without much configuration. We can add a simple in-memory cache into our server like so:
Versioning our APIs
Versioning is usually done with /v1/
, /v2/
, etc. added at the start of the API path.
app.get('/v1/employees', (req, res) => {
const employees = [];
// code to get employees
res.json(employees);
});
app.get('/v2/employees', (req, res) => {
const employees = [];
// different code to get employees
res.json(employees);
});
Thank you!
Happy coding!