REST API Architecture for Full Stack Developers

API Architecture Guidelines according to the White House specifications

Share This Post

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.

MethodDescription
GETIf 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.
POSTCreate a new resource. You call this method always without ID.
PUTUpdate an existing resource. You call this method always with ID.
DELETEDelete an existing resource. You call this method always with ID.
HEADSimilar 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.

StatusDescription
200OK, the request was successful and the response contains the requested data. You should use it for success on any requests but POST.
201CREATED, the resource has been created. Hence, use it on success only for POST.
400BAD REQUEST, generic issue in user input or request.
401UNAUTHORIZED, the user is not authenticated.
403FORBIDDEN, the user is authenticated but has no access to this resource.
500SERVER 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 returned
  • offset indicates how many records have we skipped from the beginning of the list
  • limit indicates how many records we are returning to the user now
  • single is a flag to indicate if we are returning an individual record in results 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.

Picture of Alessandro Maggio

Alessandro Maggio

Project manager, critical-thinker, passionate about networking & coding. I believe that time is the most precious resource we have, and that technology can help us not to waste it. I founded ICTShore.com with the same principle: I share what I learn so that you get value from it faster than I did.
Picture of Alessandro Maggio

Alessandro Maggio

Project manager, critical-thinker, passionate about networking & coding. I believe that time is the most precious resource we have, and that technology can help us not to waste it. I founded ICTShore.com with the same principle: I share what I learn so that you get value from it faster than I did.

Alessandro Maggio

2021-05-13T16:30:00+00:00

Unspecified

Full Stack Development Course

Unspecified