You probably know this already: in any good app, the backend is different from the frontend. In fact, the code on the client should have no direct relationship with the code on the server. Ideally, you should be able to swap the client’s code and have a new client work just fine. The same is true in the other way: changing the backend code should not affect the client. All of this is possible if we create an API. In this tutorial, we will see how to structure your API in a way that is future-proof.
What is an API?
API is the acronym of Application Programming Interface. This fancy term simply means something you design to allow other pieces of software to interact with your application. It is an interface for programs, not for users.
Imagine you have an application to book hotel rooms, much like booking.com. You will have a website, where the user can directly interact with you and make reservations. However, imagine you are targeting enterprise customers as well. A large company would love to use their own internal system where they manage travel approvals and expenditures.
Wouldn’t it be nice if that large company could use their own system, and when they click “Find me a hotel in Seattle”, your app is engaged and they make a reservation – without even checking on your site?
This is possible with APIs, as you expose the functionalities of your application in a way that other applications can use them. Easily. Then, other programmers can read the documentation and just plug-in with their software.
Designing APIs for other software, as we will see, means taking care of many different aspects. The most obvious one is that you should care about content only, and not about presenting it. No style should be provided because the requestor will parse it inside its software and present it to the user in its own way.
What is REST?
REST is another acronym, this time for Representational State Transfer. In simple terms, it is a way of thinking about APIs – one of many potential ways. However, it is one of the most widely adopted, because it is easy to understand, implement, and scale.
REST promotes the concept of resource. A resource is “something” you can perform actions onto. What that something is, of course, depends on what your API is doing. Yet, it represents an object – either physical or abstract.
If we go back to our previous example of hotel reservations, we can identify many potential resources. To start, we can identify the hotel resource. On that, we should be able to get the list of hotels and get the details of each individual hotel, including if they accept reservations. Yet, the most crucial resource for that app would be the reservation itself. On this we have more actions available: we can create, edit, and delete one. Furthermore, we may want to get the details of an existing reservation or see all of our reservations.
This approach is modular because as our application grows we can add more and more resources, in a way that is not strictly interdependent with existing ones. For example, we could add an additional resource room in that example, and if we move on to booking flights also a flight resource could be nice.
Why are APIs Important?
At first, you may think your application is consumer-only, hence you have no need to develop an API. If you think that, you may want to reconsider.
In fact, you do not start developing an API for others – you start to do it for yourself. We know that an API exposes your app to other pieces of software, but who says it is only other people’s software?
Quite the opposite, you should start to think in a modular way. With this approach, even your own frontend – the code running on the user’s client – is an external software, as seen from your backend. It should use an API. The advantage of it is that you create independent frontend and backend code and that with zero extra effort you also have the API available.
Big tech companies such as AWS adopt this approach – in fact, most features start to be available in the API first, and in the frontend only later.
How To Design Your REST API Architecture
Interestingly enough, The White House offers a guide on how to develop APIs. While it is rich in good content, that guide is relatively dry in suggestions if you are starting. In other words, it is a guide designed for seasoned developers who – more or less – already know how to implement an API. Here instead, we approach things from the bottom up, so that if you are creating your first API, you’ll know how to do it after this tutorial.
The Basics
In this initial section, we see how to use the most basic concepts of REST and RESTful applications. We will see how to structure URLs of our API, and how to use HTTP Methods.
Resource URLs
As we know, the RESTful approach is based on the concept of resource. Each resource should be available at a specific URL, which normally contains only the resource name.
For example, if we have the reservation resource, we can have an URL like the following.
https://api.example.com/reservations
Note that, while the name of the resource is singular (reservation), the name in the URL is plural (reservations). In fact, the best practice is to always use a plural resource name.
Why do we go with plural? That’s because that URL represents all the reservations. It doesn’t have to necessarily represent all the reservations whatsoever, it would normally represent all the reservations accessible to this user, but you get the point.
If we want to specify a specific resource (i.e., that very reservation), we should provide the resource ID inside the route, right after the name and separated by a slash. Obviously, the ID must be a unique way in your backend to identify resources.
https://api.example.com/reservations/5fafa40495101e4dd018d027
In this example, 5fafa40495101e4dd018d027
is the ID of the reservation we are referencing.
HTTP Methods
So far, we identified only how to name resources, but how can the client tell us what action is being requested on the resource? The client does that thanks to the HTTP Method.
HTTP offers several standard methods, that work pretty well with resources because they represent exactly what we would like to do on a resource.
Method | Description |
---|---|
GET | If on a specific resource (with ID), the user is requesting the details about that resource. When on a resource collection (no ID), the user is requesting the list of instances of that resource. |
POST | Create a new resource. You call this method always without ID. |
PUT | Update an existing resource. You call this method always with ID. |
DELETE | Delete an existing resource. You call this method always with ID. |
HEAD | Similar to get, but you get only the most important details of the resources – not all. Designed if you want to do a list of resources with basic items so that it is faster to load. |
HTTP offers us also other methods, but the ones you see in the table will do for us. As a result, we are going to call always the same URLs, but every time with a different method depending on what we want to do. Take a look at the examples below.
HEAD https://api.example.com/reservations
GET https://api.example.com/reservations
GET https://api.example.com/reservations/5fafa40495101e4dd018d027
POST https://api.example.com/reservations
PUT https://api.example.com/reservations/5fafa40495101e4dd018d027
DELETE https://api.example.com/reservations/5fafa40495101e4dd018d027
Our API is starting to take shape, at least when it comes to requests. In fact, we now have a clear idea of what kind of requests we should expect (and thus implement in our backend). The next step is to think about the response.
Formatting the Response
The user asks something and expects some sort of response. Most often, you should provide some information inside the body of the response. For example, if the user requests the details of a resource, you should provide them when you reply.
The way to do it is with JSON. JSON – JavaScript Object Notation – is a convenient way to represent complex data in a way that is structured and easy to understand, both for the user and for the computer. So, whenever you put something in the body of the request, it should be JSON.
The other thing you need to take care of is the HTTP Status. In fact, with HTTP you can define the status of every request when you provide a response to it. Was it successful? Did it have some errors? You can provide some more details with a single number.
When defining the HTTP Status, keeps things simple. Ideally, you should use only a few statuses, and if you need to add some more descriptions in the body.
Status | Description |
---|---|
200 | OK , the request was successful and the response contains the requested data. You should use it for success on any requests but POST . |
201 | CREATED , the resource has been created. Hence, use it on success only for POST . |
400 | BAD REQUEST , generic issue in user input or request. |
401 | UNAUTHORIZED , the user is not authenticated. |
403 | FORBIDDEN , the user is authenticated but has no access to this resource. |
500 | SERVER ERROR , ideally, you should have the least possible of those. Whenever you have one, it means trouble. |
Some people also use 204 NO CONTENT
with DELETE
requests. This means that the response is correctly coming with an empty body. While this is a valid approach, I recommend instead returning the entire deleted resource in that case. Hence, there is no need to use that status code.
Versioning
Now that you got the basics of REST APIs, we can start to move into more interesting territories. Beyond the basics, the very first thing you need to learn is versioning.
Your API will grow over time, it will expand its features and some features will retire. Thus, it is important to have an easy way to rewire our frontend and other third-party applications in the transition phase – that is, when things are changing.
We do that particularly easy by including the API version inside the URL. When doing so, we want to use the word api
in the URL, to tell that the version is referring to the API. Then, we provide the version number, normally starting with “v” (short for “version”).
Providing a version number does not mean providing minor and patch number, but only major. If you know Semantic Versioning, each version of a software has three parts: Major, minor, and patch – each represented by a number, separated by dots (M.m.p
).
- Major version represents indeed a principal version of the app. Each change to this means there are user-impacting changes.
- Minor version represents a significant change inside the same major, with no or little impact to the user, and always without revolutionizing the experience.
- Patch is a small fix or add, with no impact to user experience (except fixing what was not working as expected)
As you can see, since only the major version is the one that impacts potential API users, we should only report that in the URL. Only that first number. So, if our APP is currently at version 1.7.43, our URLs should start with:
/api/v1
If we want to rewrite the URLs from our previous examples, we can do like so:
HEAD https://api.example.com/api/v1/reservations
GET https://api.example.com/api/v1/reservations
GET https://api.example.com/api/v1/reservations/5fafa40495101e4dd018d027
POST https://api.example.com/api/v1/reservations
PUT https://api.example.com/api/v1/reservations/5fafa40495101e4dd018d027
DELETE https://api.example.com/api/v1/reservations/5fafa40495101e4dd018d027
Remember: always use versioning. It will save you a lot of pain in the future.
Advanced URLs
Now that we have a solid foundation of API knowledge, we can discuss a little bit further the URLs and resource names.
Since a resource represents an object, its name should include a name or names, but never a verb. If it is a multi-word name, you should separate its parts with a hyphen (e.g., /api/v1/expense-report
). Not with an underscore, with camel case, with a unicorn, or with whatever you want. With a hyphen, plain and simple.
You shouldn’t use verbs because the verb naturally tells what you want to do, and for that we have the HTTP method.
In some cases, you may have resources inside other resources. For example, you may have the hotels
resource, and each hotel contains its rooms
resources. We can represent this by nesting the URL.
https://api.example.com/api/v1/hotels/5fafa40495101e4dd018d027/rooms
As you can see we have three levels now: the type of root resource (hotels
), the ID of that specific hotel (5fafa40495101e4dd018d027
), and the sub-resource (rooms
).
Nesting resources is feasible but never go beyond resource-id-resource nesting. If you nest more than that, you are doing something wrong. That’s because, more often than not, the user needs to know all the IDs in the URL before making a query. Hence, if your URL has only one ID, the user may need to do one query before making the actual request he wants. If your URL contains 5, the user may need to do 5 preliminary requests – definitely not goods.
Hence, try to break any nested structure beyond third level.
Crafting Better Responses
As a newbie, I started creating APIs that simply returned the requested resource. This can pose some limits in the long run, particularly when you work with pagination or other more advanced features. Instead, you should follow the guidelines below.
Always Provide Metadata
Metadata is another fancy term that means data about the data. In other words, it is something that describes the response we are sending so that the client can better interpret it.
Hence, the root of your JSON in the response shouldn’t be the object itself, but rather an object with two properties: metadata
and results
(or metadata
and data
, if you prefer).
In the results
property, you should provide the object representing your resource, or the array of objects in case the query is against multiple objects. In other words, results
are what the user is actually requesting.
Instead, metadata
should contain pagination information and error information, if any. Pagination will be useless when getting a single resource, but it can be useful in any case. I normally divide the metadata
object into resultset
(about pagination), and error
.
{
"metadata": {
"resultset:" {
"count": 100,
"offset": 20,
"limit": 10,
"single:" false,
},
"error": null,
},
"results": [ ... ]
}
Here is how you should interpret those data;
count
indicates the total number of records matching the query, regardless of the ones returnedoffset
indicates how many records have we skipped from the beginning of the listlimit
indicates how many records we are returning to the user nowsingle
is a flag to indicate if we are returning an individual record inresults
or an array
On the other hand, the error
property is null because there is no error. Yet, when it comes to providing some information about the error, we can open a whole different discussion.
Handling Errors
The error
object should be provided as null if there is no error, otherwise, it should carry the following properties.
status
Same status as the HTTP status, so that the user can fetch the status of the request from the same place where you have all error-related information.developerMessage
indicates a message to help a developer troubleshoot.userMessage
indicates a message that may be shown to the user.errorCode
is a numeric code unique to this error defined by your application.moreInfo
an URL (or more) to the documentation that explains this error.
To make an example in-code, this is what the White House API shows on their GitHub page.
{
"status" : 400,
"developerMessage" : "Verbose, plain language description of the problem. Provide developers
suggestions about how to solve their problems here",
"userMessage" : "This is a message that can be passed along to end-users, if needed.",
"errorCode" : "444444",
"moreInfo" : "http://www.example.gov/developer/path/to/help/for/444444,
http://drupal.org/node/444444",
}
Extra Tips
We can improve our app by adding some extra features to the API. To start, sometimes JSON is not the only format that we want to expose to the user – both for requests and responses. In that case, we need may want to let the user provide the format of the API.
The best way to do this is to add an extension to the API URL, for example .json
if we want to interact with JSON, and .xml
if we want to interact with XML.
GET https://api.example.com/api/v1/reservations.json
GET https://api.example.com/api/v1/reservations.xml
Both API calls will do the same thing, but in different formats.
Another good improvement is to accept URL parameters for queries against multiple records. Ideally, we want to give the user the ability to define how the query should be implemented in terms of limits, offset, and so on. I like to do it by prefixing URL parameters with a tilde. Why prefixing with a tilde? In this way, I can use non-prefixed URL parameters to query against fields in my records (e.g. give me all active reservations).
GET https://api.example.com/api/v1/reservations.json?status=active&~offset=20
Finally, you may want to add some mock support. This enables the user to make some mock requests and get some mock responses, without interacting with actual data. The best way to indicate a mock request is to let the user specify it through a mock
URL parameter.
GET https://api.example.com/api/v1/reservations?mock
In Conclusion
In this extensive tutorial, we saw everything that you need to know to build a stable and reliable API. Even better, with all these guidelines you will build a future-proof API, something that you can easily maintain for years, and that other developers can easily adopt and understand.
Of course, your journey to becoming a proficient Full-Stack Developer does not end there. There are some other things you should check to create a modern application, particularly Unit Testing and Microservices. Don’t worry, those are coming in the following articles.