How to design a RESTful API architecture from a human-language spec

A process to build RESTful APIs that solve users’ needs with simplicity, reliability, and performance.

By Filipe Ximenes and Flávio Juvenal
December 21, 2017
Sofia Sofia (source: tpsdave)

Like almost all software, an API needs to reflect the needs of the humans who interact with it. An API is somewhat different from a GUI or other user interface because it interacts with a programmer rather than directly with the end user. However, the principles of designing for the user still apply: you must think of the background knowledge brought by the programmers and the constraints under which they are operating, such as the bandwidth of mobile devices.

Although many programmers release uncreative APIs that merely expose database functions (the well-known CRUD operations of Create, Read, Update, Delete), useful APIs go beyond that and reflect the functions requested by the user, such as Reserve or Buy. They offer terms and processes that are intuitive to the programmer at the other end—including meaningful error status messages—and are structured to maximize efficiency. This series of three articles takes you step by step through the process of designing useful APIs.

Learn faster. Dig deeper. See farther.

Join the O'Reilly online learning platform. Get a free trial today and find answers on the fly, or master something new and useful.

Learn more

In this article, we assume you already have a human-readable specification of a system, and we’ll teach you a process for designing a RESTful HTTP API architecture from it. By RESTful API we mean an API that follows Representational State Transfer (REST) architectural style. REST is a very popular approach to building APIs because it emphasizes simplicity, extensibility, reliability, and performance.

Let’s briefly look at each of those concepts to see how using a RESTful architectural approach affects the way your API behaves for its users1 :

  • Simplicity: The way to interact with API resources is well-defined and strict: you have a clear and simple path to follow concerning what you can and cannot do. Interaction is stateless: requests have all data needed to move resources to the next state.
  • Extensibility: Support for representing the same resources with different formats or even versions, ability to add new resources without changing others, etc.
  • Reliability: Clear separation of actions with and without side-effects, the possibility of retrying requests with idempotent actions, etc.
  • Performance: Caching made possible via well-defined semantics, scalability made possible because server resources can be isolated in each request due to stateless interaction, etc.

The process for you’ll learn in this article for designing a RESTful HTTP API architecture has two steps:

  1. Identify nouns and verbs from the system spec
  2. Extract URLs and the methods they respond to from those nouns and verbs

With those simple things, we’ll have the basis of our API. It will be enhanced with other considerations in the next two articles.

Start with the basics: REST key concepts

Let’s review a few key concepts of RESTful APIs as we start reasoning about our own application. First of all, we need to think about the basic elements used in REST:

  • Resources – You can think of those as the “nouns” of your system. For example, in a food-delivery service API, the nouns would be restaurant, menu, menu item, restaurant owner, etc. Those nouns are what hold the State, the “S” in REST.
  • Representations – Representations are the way API clients see the resources. A RESTful API never hands resources directly to a user. Interactions happen only via representations of the real resource. For example, you can store all the menu items for a restaurant in a database table, but the representations of these items in your API might be a list of names and prices, perhaps filtered to reflect restrictions specified by the user. To do this, we use media types such as JSON and XML. That’s why REST is “RE”presentational.
  • Actions – Because API clients do not have direct access to resources, they need actions to alter the state of a resource. Those actions are the verbs of our system. They are what “transfer” the state, the “T” of REST.

It’s important to understand that REST is not a protocol, but an architectural style. It’s a set of rules and guidelines that define how a system should function. REST is not limited to HTTP, but in practice, when people talk about RESTful APIs, they are likely talking about APIs using the HTTP protocol. This article is also about REST applied to HTTP applications. We will use HTTP tools to build the resources, representations, and actions of our application.

One of the main authors of HTTP is also the creator of REST. No wonder HTTP has parts that fit very well to these REST basic elements:

  • URLs – Uniform resource locators, as the name states, are what people and programs use to locate resources from servers across the Internet. They locate REST resources.
  • Media Types – These specify how the data in requests and responses look like, i.e., the REST representations. Currently, the most popular media types are JSON, HTML, and XML.
  • Methods – HTTP built-in methods (such as GET, POST, PUT, DELETE) are what we use to implement REST actions. Each HTTP method has characteristics that should be taken into account when implementing the actions. Methods are called over URLs and have data that is represented with media types.

Now that you’ve got those key terms in mind, let’s look at a very simple example of a RESTful API over HTTP. Imagine an API that gives the current temperature in New York City. The interaction with it happens in a request-response cycle between client and server. In simplified terms, the API interaction in practice is:

Request: GET http://info-about-nyc.com/temperature/
Response: {"celsius": 14, "fahrenheit": 57}

Behind the scenes, what happened above was:

  1. The client sent a GET method to the server via the http://info-about-nyc.com/temperature/ URL.
  2. The server responded with a JSON representation of the temperature containing the values in Celsius and Fahrenheit.

That’s great, but too basic. Let’s move to a more complex example using HTTP URLs, media types, and methods to implement REST resources, representations, and actions. We’ll use an example application spec to show you a process for designing a RESTful API.

Reviewing the Spec for our Example Application

We’re going to design a RESTful API for an app of a bike rental service. This service has automated stations2 around a city. Users can rent a bike using a mobile app. All stations are connected to the same server, as is the app. Stations know when a request to release a bike is made and when a bike is returned to a station. To rent a bike, the user follows these steps:

  1. Before going to a station, the user sees on the app the list of all stations. Each station has a name, a latitude and longitude location, and a numeric identifier. Also, the app shows the quantity of available bikes on each station. This is useful because some stations may not have bikes available for rent, i.e., all have been rented.
  2. The user goes to one of the stations with available bikes.
  3. Once at the station, the user decides which bike they want. Like the stations, each bike has a number.
  4. The user rents a bike by inputting on the app the station number, the available bike number, and the destination station number.
  5. The station releases the chosen bike.
  6. The user rides the bike.
  7. After the ride, the user returns the bike to the destination station they specified.

Besides the bike rental itself, the service has other additional constraints and features:

  • Each user can only rent one bike at a time.
  • The user can see the history of their rentals, including the current active rental (if one exists). The history of rentals includes rental start dates and end dates.
  • After a rental, the user can change the destination station through the app. However, this change is allowed only during the first 10 minutes after the rent.
  • The user can also cancel the current active rental within 10 minutes. After canceling, the user must return the bike to the same station it was taken from.

Remember: we’ll just design the API for the mobile app of this bike rental service. We won’t worry about the communication between the server and the stations. Also, we’ll assume all users already have accounts and subscriptions for the service. In other words, we won’t care about user management or payment.

Now let’s see the process we follow to design our RESTful API.

From Spec to RESTful API

Step 1: Identifying Nouns and Verbs

We’ve seen that REST is made of resources, representations, and actions. We’ve also seen that resources are like nouns and actions are like verbs. So, identifying the nouns and verbs available at a system spec is a good way to start our spec to API process. By reading the spec, we can get the following list of nouns and verbs related to the system use cases:

Nouns:

  • City
  • User
  • Station
  • Bike

Verbs:

  • View (user views the list of stations)
  • Rent (user rents a bike)
  • Show (app shows the quantity of available bikes at a station)
  • Release (station releases a bike)
  • Ride (user rides a bike)
  • Return (user returns a bike)
  • View (user views the history of rents)
  • Change (user changes destination station)
  • Cancel (user cancels the active rent)

But we won’t need REST resources for all of those nouns, nor REST actions for all of those verbs. We can remove the following:

  • City noun. We can assume each user is interested only in the city they’re currently located in.
  • User noun. We already said we can suppose all users have accounts.
  • Bike noun. The service doesn’t offer any additional info about bikes, except if they’re available or not.
  • Release verb. We’re designing the API for the mobile app, not for the station.
  • Return verb. Same as above.
  • Ride verb. The service doesn’t care how the user rides the bike.

After removing those, we’ve ended up with this new list:

Nouns:

  • Station

Verbs:

  • View (user views the list of stations)
  • Rent (user rents a bike)
  • Show (app shows the quantity of available bikes at a station)
  • View (user views the history of rents)
  • Change (user changes destination station)
  • Cancel (user cancels the active rent)

Great, now the list is more realistic to the mobile app API use cases. The next step is to convert the nouns to HTTP resources with URLs and the verbs to HTTP methods.

It’s easy to convert nouns into HTTP resources with URLs. For example, the noun “station” can become a resource located by the URL path /stations/ in our API. But there are some gotchas in the conversion of verbs to HTTP methods. Let’s see why.

A word on HTTP “verbs”

One limitation we have while mapping a spec to a RESTful API is the restricted set of verbs HTTP provides. We can only use the documented HTTP methods: GET, POST, PUT, PATCH, DELETE, etc. As you can see, we don’t have a “RENT” verb, even though at first glance our application requires it.

Instead of custom verbs, we must define custom nouns for our RESTful API. Anything can be a noun. Therefore anything can be a resource that’s mapped by a URL. Even if it doesn’t look like a noun at first sight. “RENT” fits more into a verb, but you can noun-ify it to make it a resource. Think about the “Rent” noun, not the “RENT” verb. Then you’ll see that you can “add a Rent”. And HTTP fits here because it has the POST verb for adding things. See? Just by changing the way we think about noun and verbs we can adapt our application to HTTP. The correct way to rent something via HTTP is to POST a Rent.

Thus, to properly design our RESTful API we need to know really well the meaning of each HTTP method. What are the semantics behind each method? How should we use them? Let’s see:

  • GET – Should be used to retrieve data from the server. Means “give me a representation of this resource identified by this URL”. It’s worth noting that we must never use GET to change resources. For this, we have the following other methods.
  • POST – Should be used to provide data to the server. Means “process the resource representation as a new subordinate of this URL”. In practice, POST is mostly used for creating resources. The client specifies the resource representation in the request body and makes a POST to the URL that holds the collection of resources. 3
  • PUT – Should be used to update existing server resources by replacing their old state with a new one. Means “use this representation to replace the resource identified by this URL”. 4
  • DELETE – Should be used to remove server resources. Means “delete the resource identified by this URL”.

By following the semantics of each method, we can design a truly RESTful API. Now let’s get back to the next step: converting the nouns to HTTP resources with URLs and the verbs to HTTP methods.

Step 2: Extracting URLs and their methods from nouns and verbs

We’ve seen that mapping nouns to resources is as easy as Station => /stations/. But verbs are different. Some verbs are easily mapped to a HTTP method, because sometimes the semantics match. Other verbs will need to be noun-fied to become resources, as we’ve seen in the last section with “Rent”.

Besides that, we also need to care about which data from resources will be included in request and response bodies. For example, on a POST request for creation, the body needs to include the resource representation to be created. Another example, a GET response body needs to contain a resource representation, but a DELETE response body does not.

Summarizing, here’s what we need to answer here to design our API:

  • Which HTTP methods map to our verbs?
  • Which resources and corresponding URLs should we create for our nouns and noun-fied verbs?
  • For each URL + method, which resource data should be included in the request body?
  • For each URL + method, which resource data should be included in the response body?

The result of answering those questions is the basis of our RESTful API design: combinations of URLs and the HTTP methods they respond to, along with what’s included in the request and response bodies.

To achieve this, let’s analyze each of our verbs and think about the nouns they interact with:

View (user views the list of stations)

Which HTTP method maps to “View”?
“View the list” means the API should show the list. Which HTTP method fits that? The GET method.

What’s the noun here, what’s the resource? Which URL fits this resource?
We need a GET request to the list/collection of stations. “Collection of stations” is the resource here. “Station” is already a defined noun. Thus, we should implement a /stations/ URL that accepts GETs.5

What should be passed in the request body?
Since we’re only GETting, we don’t need to pass anything.

What should be included in the response body?
GET /stations/ needs to return a representation of the collection of stations. This representation could be a JSON list, for example. Each station has a name, a location, and identifier, so we need to include these in the representation. To represent the location, we’ll use a JSON list with the latitude and longitude as floats.

This means we need to have something like:

Request: GET /stations/
Request body: (empty)
Response body:
[{
	"id": 1,
	"name": "John St",
	"location": [40.7, -74]
},
{
	"id": 2,
	"name": "Brooklyn Bridge",
	"location": [40.7, -73]
}]

Rent (user rents a bike)

Which HTTP method maps to “Rent”?
What’s the noun here, what’s the resource? Which URL fits this resource? The noun here seems to be “bike”. But if you pay a little more attention, you will notice that this is not about the bike object. Instead, we are actually talking about the bike “rental”—or the act of renting. That’s why we need to noun-ify the “Rent” verb. Remember how in HTTP we use POST for creating resources? So a “Rent” action can be represented as the POSTing of a rent resource representation. This can be done with a /rents/ URL that accepts POSTs.

What should be passed in the request body?
The POST request needs to include the station number, the bike number to be rented, and the destination station. These are the parts of the representation of a rent.

What should be included in the response body?
Nothing. If the POST succeeds, the API client will know the rent has been made.6

Here’s the practical example:

Request: POST /rents/
Request body:
{
	"origin_station_id": 1,
	"bike_number": 10,
	"destination_station_id": 2
}
Response body: (empty)

Show (app shows the quantity of available bikes at a station)

Which HTTP method maps to “Show”?
Easy, you already know this one: GET.

What’s the noun here, what’s the resource? Which URL fits this resource?
The noun is “quantity of available bikes at a station”. But we already have a URL and method that returns info about stations: GET /stations/. Why not just add the number of available bikes to each station in its response? Let’s update GET /stations/.

What should be passed in the request body?
Nothing. This is a GET.

What should be included in the response body?
We’re working here with GET /stations/. We just need to add the info about the quantity of available bikes at each station.

In practice, this means:

Request: GET /stations/
Request body: (empty)
Response body:
[{
	"id": 1,
	"name": "John St",
	"location": [40.7, -74],
	"available_bikes_quantity": 10
},
{
	"id": 2,
	"name": "Brooklyn Bridge",
	"location": [40.7, -73],
	"available_bikes_quantity": 2
}]

View (user views the history of rents)

Which HTTP method maps to “View”?
GET.

What’s the noun here, what’s the resource? Which URL fits this resource?
We already noun-ified “Rent”. We can now use the /rents/ URL to return the collection of user rents. Besides accepting POSTs, this URL can also accept GETs. A GET request can return the history of the rents made by the user.

What should be passed in the request body?
Nothing. This is a GET.

What should be included in the response body?
The collection of user rents. It’s a good practice to always return an identifier in the representations. It makes easier for API clients to manipulate rents, so let’s return the ID of each rent in the collection. Remember that on the creation of the rent, we pass the origin station, the bike number, and the destination station, so we can include those here too. Also, according to the spec, we need to include the rent’s start date, end date, and some flag indicating whether the rent is the currently active one.

We can do it like this:

Request: GET /rents/
Request body: (empty)
Response body:
[{
	"id": 1,
	"origin_station_id": 1,
	"bike_number": 10,
	"destination_station_id": 2,
	"start_date": "2017-06-08T19:30:39+00:00",
	"end_date": null,
	"is_active": true
}]

Change (user changes destination station)

Which HTTP method maps to “Change”?
PUT is the method for updates.

What’s the noun here, what’s the resource? Which URL fits this resource?
Think what the user is changing the destination station of. Of the active “Rent”! We still don’t have an URL for a single rent, but making one is as easy as: /rents/{id}/. Since GET /rents/ returns the IDs of the user rents and which one is the active one, the mobile app will be able to use that to build the URL of the rent to be changed.

What should be passed in the request body?
A representation of what can be updated: the new destination station identifier number.

What should be included in the response body?
Nothing. If the PUT succeeds, the API client will know the change has been made.

The example:

Request: PUT /rents/321914/
Request body:
{
	"destination_station_id": 2
}
Response body: (empty)

Cancel (user cancels the active rent)

Which HTTP method maps to “Cancel”?
Canceling a Rent can be thought as deleting it. DELETE can be used here.

What’s the noun here, what’s the resource? Which URL fits this resource?
The active rent is the resource. The URL will be /rents/{id}/, where id is the identifier of the active rent.

What should be passed in the request body?
Nothing. The ID contained in the URL is enough to locate the rent to be canceled.

What should be included in the response body?
Nothing. If the DELETE succeeds, the API client will know the change has been made.

The example:

Request: DELETE /rents/321914/
Request body: (empty)
Response body: (empty)

Phew! After meditating about all the verbs in our spec and deriving new nouns as necessary, we mapped all features of the API for the mobile app. We converted a human-language spec into a list of URLs7 with the HTTP methods they respond to. Also, we know which data those URLs expect on requests and return on responses.

Next steps

We have the basic design of our RESTful API! However, note that we ignored the error conditions. What happens if the user tries to change the destination station more than 10 minutes after the rent? How should the API respond? In the next post, we’ll learn how to use HTTP status codes to communicate success and error conditions.


  1. Additionally, since HTTP is the standard protocol for web, having an RESTful API over it enables communication between platforms running on different programming languages or technologies.
  2. We know automated stations for bike sharing services are becoming obsolete. There are companies implementing bike rental without stations. We choose an example with stations to explain better the REST concepts. Having “station” as a resource is important for the article.
  3. It’s worth noting POST can also be used for functionalities that doesn’t look like a creation of a resource at a first glance. For example, it makes sense for an API to allow POST to the /password-reset/ URL. One could argue that’s still creation if you think you’re “creating” a password reset “order”. This is OK. The problem is using POST as a wildcard method, that does things like retrieval, edition, deletion, etc. This is not RESTful, so if you see the necessity to break methods semantics, try rethinking your resources and URLs. Otherwise your API will be just a bunch of POST interactions with unclear semantics and will look like RPC over HTTP like old non-RESTful APIs.
  4. There is also another HTTP method used for updates: PATCH. We omitted it because it’s not often used and when it is, it’s used wrongly. A PATCH request should contain in its body the instructions to change a server resource. In other words, it carries a representation of changes to be applied at the resource, not the whole updated resource itself, like in PUT. That’s the difference between them. Those different representations reflect the difference between a partial and a full update. Most RESTful APIs accept both partial and full updates in PUT, with the representation being the resource, not the changes to the applied on it. That’s why PUT is more common than PATCH.
  5. Although REST does not defines how we should name URLs, it’s extremely important to pay attention when doing this. URL names will guide users of your API through the resources and facilitate their understanding of what they are doing.
  6. A RESTful HTTP API should use status codes to tell the client that a request succeeded or failed. We’ll talk about them in a following post.
  7. There’s more than one way to name things. Other people could come up with completely different resources for the same spec we started from. The good news is that because HTTP is a protocol, the use of its methods is standardized, so there wouldn’t be much variation in that regard.

Post topics: Software Architecture
Share:

Get the O’Reilly Radar Trends to Watch newsletter