This chapter covers
In the previous chapter, you learned how to identify an API’s goals—what users can achieve using it. For a Shopping API, some of these goals could be search for products, get product, add product to cart, check out cart, or list orders. These goals form the API’s functional blueprint that we will use to design the actual programming interface that is consumed by its users (developers) and their software. To design this programming interface, we will transpose these goals and their inputs and outputs according to an API style, as shown in figure 3.1.
REST stands for Representational State Transfer. Here, the REST API style transposes the get product goal into a programming interface. It is represented by a GET /products/{productId}
request, where productId
is an input parameter (here its value is P123
), and a 200 OK
is a response with some output data consisting of the reference
, name
, and price
properties. How do we design such a programming interface?
Representing goals using REST or any other type of programming interface requires that you first understand how it works. Indeed, what do these GET
, /products/{productId}
, or 200 OK
mean, for example? Without knowing that, you will be unable to actually design such a programming interface. Once you have some basic knowledge, you can analyze the goals and represent them according to the chosen API style. You also have to design data exchanged through the API more accurately than what we did when filling in the API goals canvas. The process is similar to what you do when programming.
That sounds quite straightforward, doesn’t it? But things are not always that simple. After learning how to transpose basic goals to a programming interface, you might realize that some of your goals cannot be represented easily. In such cases, you have to find a path between user-friendliness and compliance with the chosen API style in order to come up with the best possible representation.
After all this, you may wonder what Representational State Transfer actually means and why it has been chosen as the main example programming interface for this book. To teach API design, why use REST APIs? Why are these APIs better than others? Although they are widely adopted, there’s a far more important reason behind this choice: REST APIs are based on the REST architectural style, which relies on solid foundations that are useful to know when designing any type of API. We’ll get to that soon, but first things first. Let’s talk about some basic REST API principles.
To gain sufficient knowledge about REST APIs in order to actually design one, we will analyze the REST API call made by a consumer of the Shopping API to get a product’s information, as seen in this chapter’s introduction (figure 3.1). We’ll take for granted that the REST representation of this goal is GET /products/{productId}
, and we will work on the GET /products/P123
example. If you remember section 1.1.1, you should guess that this request has something to do with the HTTP protocol. This analysis will show us how HTTP is actually used by this call. After that, we will be able to dive into the HTTP protocol and the basic principles of REST APIs.
What happens when a consumer wants to complete the goal get product? Or, more specifically, what happens when they want to get detailed information about a product with the ID P123
from the products catalog using the REST Shopping API? Consumers have to communicate with the server hosting the API using the HTTP protocol, as shown in figure 3.2.
Because this goal is represented by GET /products/{productId}
, the consumer has to send a GET /products/P123
HTTP request to the Shopping API server. In reply, the server returns an HTTP response containing 200
OK
, followed by the requested product’s information. (Note that this HTTP exchange has been simplified in order to focus only on the elements that matter to us.)
The request is composed of the GET
HTTP method and the /products/P123
path. The path is an address identifying a resource on the server; in this case, the P123
product in products
. The HTTP method indicates what the consumer wants to do with this resource: GET
means that they want to retrieve the resource. From a functional perspective, such a request means something like, “Hi, can I have the information on the product whose ID is P123
?” But from the HTTP protocol’s perspective, it means, “Hi, can I have the resource identified by the /products/P123
path?”
The first part of the response is composed of the 200
HTTP status code and its OK
reason phrase. The HTTP status code tells us how the processing of the request went. Thanks to the reason phrase, we can guess that the 200
HTTP status code means that everything went OK. The second part of the response is called the response body. It contains the content of the resource identified by the path in the request, which, in this case, is the P123
product’s information represented as JSON data.
From a functional perspective, the response returned by the API server basically means, “Sure, here’s the requested product’s information.” From the HTTP perspective, it means, “No problem, here’s the requested resource’s content.”
Now you know how consumers can call the Shopping API to achieve the get product goal. But the HTTP protocol is not made just to retrieve JSON documents.
HTTP is the foundation of communication for the World Wide Web. It is a programming language-agnostic protocol that is designed to allow the exchange of documents (also called resources) between applications over the internet. HTTP is used by a wide range of applications, the best known of which are web browsers.
A web browser uses HTTP to communicate with a web server hosting a website. When you type a URL (such as http://apihandyman.io/about) in the browser’s address bar, it sends a GET /about
HTTP request to the server hosting apihandyman.io
, just like when an API consumer sends a request to a REST API server. The response sent by the server contains a 200 OK
HTTP status followed by the HTML page corresponding to the URL.
Browsers use the protocol to retrieve any type of resource (document): HTML pages, CSS files, JavaScript files, images, and any other documents that are needed by the website. But that’s not its only use. When you upload a photo to a social networking website, for example, the browser uses the HTTP protocol, but this time to send a document to a server. In this case, the browser sends a POST /photos
request with a body containing the image file. The HTTP protocol can therefore also be used to send the content of a resource.
HTTP requests and responses always look the same regardless of what is requested and what is the result of the processing of the request (figure 3.3).
Whatever its purpose, a basic HTTP request contains an HTTP method and a resource’s path. The HTTP method indicates what is to be done with the resource identified by the path. You have already seen two HTTP methods—GET
to retrieve a resource and POST
to send one—and you will discover more later in this chapter.
This first part of the request can be followed by a body containing the content of the resource that needs to be sent to the server to create, update, or replace a resource, for example. This content can be of any type: a JSON document, a text file, or a photo, for example.
As mentioned previously, the HTTP response returned by the server always contains a status code and explanatory reason phrase. This indicates how the processing of the request went—if it was a success or not. You have so far seen only one HTTP status code, 200 OK
, but you’ll discover more later in this book (like the well-known 404 NOT FOUND
that will be explained in section 5.2.3). This first part of the response can be followed by a body containing the content of the resource that was manipulated by the request. Like the request’s body, this content can be of any type.
The HTTP protocol seems quite simple. But are REST APIs that use this protocol that simple?
You have seen that using the REST Shopping API for the get product goal, consumers have to send an HTTP request to the server hosting the API. This request uses the GET
HTTP method on the /products/{productId}
path that identifies the product. If everything is all right, the server returns a response that contains an HTTP status indicating that, along with the product’s data. You also saw that if a web browser wants to retrieve a page from my apihandyman.io blog, it sends an HTTP request. This request uses the GET
HTTP method on the page’s path (/about
, for example). If everything is all right, the web server also returns a response containing a 200 OK
HTTP status and the page’s content. Exactly the same thing happens!
Both the web server and the Shopping API server expose an HTTP interface that respects the HTTP protocol’s expected behavior. A basic REST API not only uses the HTTP protocol, it totally relies on it, as shown in figure 3.4.
To let its consumers achieve their goals, a REST API allows them to manipulate resources identified by paths using standardized HTTP methods. A resource is a functional concept. For example, /products/{productId}
identifies a specific product in the products catalog. This path identifies a product resource. The GET
HTTP method represents the retrieve action that can be applied to this resource to actually get the product.
A REST API call is nothing more than an HTTP call. Let’s see now how we get from the get product goal to GET /products/{product’s reference}
—how we transform goals to HTTP method and path pairs.
You have discovered that a REST API represents its goals using the HTTP protocol. Goals are transposed into resource and action pairs. Resources are identified by paths, and actions are represented by HTTP methods. But how do we identify these resources and actions? And how do we represent them using paths and HTTP methods?
We do what has always been done in software design. We analyze our functional needs to identify resources and what happens to them before transposing them into a programmatic representation. There are many software design methods that you could use to identify resources and what you can do with them based on some specifications, like the API goals canvas from the previous chapter. This book, however, shows a very simple method in four steps (see figure 3.5).
First, we have to identify resources (functional concepts) and their relationships (how they are organized). Then we have to identify for each resource the available actions and their parameters and returns. Once this is done, we can proceed to the actual HTTP programming interface design by creating resources paths and choosing HTTP methods to represent actions.
In the following sections, we’ll walk through this process in more detail. Here, we only talk about the nominal case, when everything is 200
OK
. We will talk about error handling in section 5.2.
The API goals canvas you discovered in chapter 2 describes who the users are, what they can do, how they do it, what they need to do it, and what they can get in return. We can use this information to identify the API goals that we will transpose into a REST API. To practice on a basic but complete example, I have enhanced the manage catalog part of the Shopping API goals canvas we began working with in chapter 2 (figure 3.6).
As you can see in this figure, when managing the product catalog, admin users can add a product to the catalog. They can also retrieve a product’s information and update, replace, or delete it. Finally, it’s possible to search for products using a free query. Figure 3.7 shows how we simply analyze the API goals and list all the nouns to which the goals' main verbs apply for identifying resources.
When we add a product to the catalog, the main verb is add, and it is applied to both the product and catalog resources. When we get a product, the main verb is get, and it is applied to the product only. But when we search for products in the catalog using a free query, free query is a noun, not a resource, because the search verb does not apply to it directly. Therefore, we can identify two resources: catalog and product.
Now let’s see how these resources are related. To discover the resources' organization, we list goals mentioning more than one resource (see figure 3.8).
We have two goals dealing with more than one resource. In the first one, we add a product to the catalog. In the second one, we search for products in the catalog.
Resources may or may not have relationships to other resources, and a resource can contain some other resources of the same type. Such a resource is called a collection resource or simply collection. In our case, the catalog resource is a collection: it contains product resources. If we were designing an API related to city planning, a city could be a collection resource containing many building resources. A resource can also be linked to other resources; for example, a building resource could be linked to an address resource.
We have identified our catalog and product resources and how they are related. What can we do with them?
A REST API represents its goals through actions on resources. To identify an action, we take the goal’s main verb and link it to the resource to which it applies, as shown in figure 3.9.
This is straightforward for goals with a single resource, such as get product, where the verb get applies to the product resource. But what about the goals add product to catalog and search for products in catalog using a free query? Do we link add and search to product or catalog? We add a product to the catalog and we search for products in the catalog; in this use case, add and search are linked to the catalog resource. That means we link the verb to the main resource (catalog) that is used or modified—the other one (product) is only a parameter or a return.
These actions might need some additional parameters and can return some information. Fortunately, we’ve already identified these parameters and returns; the API goals canvas comes with a complete list of inputs (parameters) and outputs (returns), as shown in figure 3.10.
We just need to filter the inputs because some of them can be resources to which the action is applied. When we apply add to the catalog resource, we need some product information as an input; and, in return, we get the newly created product resource. We provide a free query to the search action that is applied to the catalog resource, and this action returns the matching product resources. We then do exactly the same thing for actions applied to the product resource, and we’re done! We have identified all the resources and their actions, including parameters and returns, by analyzing the API canvas and its goals (figure 3.11).
As you can see, this process is not really different from what you do when you design the implementation of software. It takes a long time to describe how to do this in a book, but actually doing it takes only a couple of minutes with the API canvas. Let’s now see how we represent all of this with the HTTP protocol. We will start by representing resources with paths.
By analyzing the API goals canvas, we have identified two resources: catalog and product. We also have discovered that the catalog resource is a collection of product resources. How can we design these resources' paths? Figure 3.12 shows how to do so.
A REST resource’s path only needs to be unique. To identify the catalog, we could use a /c
path. For products, we could use the product reference or technical ID and build a /{productId}
path with it (/P123
, for example). Such variables in paths are called path parameters. The /c
and /{productId}
paths are perfectly valid REST paths because they are unique.
But let’s be frank. What would you think about such an interface in the real world? This is not really consumer-friendly; and, remember from chapter 2, we should always design an API for its users. It would be better to choose paths that indicate explicitly what they represent. Why not simply use /catalog
for the catalog resource and /product-{productId}
for the product resource? That sounds good, but these paths are not the only possibilities, as figure 3.13 shows.
To improve user-friendliness, the relationship between the catalog and product resources could be reflected in the paths like in the folder hierarchy you find on a filesystem. Each product resource is an item in the catalog collection identified by /catalog
, so we could choose the path /catalog/{productId}
to represent a product. We could also explicitly indicate that a catalog is a collection of product resources by using a /products
path, with a product from this collection being represented by the /products/{productId}
path. That’s a lot of options! Figure 3.14 shows them all.
From a pure REST perspective, all of these representations are valid. Even if we’ve already discarded cryptic paths such as /c
and /{productId}
because they are obviously not consumer-friendly, we still have many possibilities. A catalog resource could be represented by /catalog
or /products
and a product resource by /product-{productId}
, /catalog/{productId}
, or /products/{productId}
.
Although there are no official REST rules regarding resource path design (apart from uniqueness), the most widely adopted format is /{plural name reflecting collection’s item type}/{item id}
. Using resource paths exposing resource hierarchy and using plural names for collections to show the collection’s item type has become a de facto REST standard.
In our example, a catalog should therefore be identified by /products
and a product by /products/{productId}
. This structure can be extended to multiple levels as in /resources/{resourceId}/sub-resources/{subResourceId}
.
We’re almost done! We have identified resources and their actions, and we have designed our resource paths. Here comes the final step, representing actions with the HTTP protocol.
Let’s start with the catalog resource and its add action, as shown in figure 3.15.
The HTTP representation of the goal add product to catalog is POST /products
. When we add a product to the catalog resource identified by /products
, we actually create a product resource using the provided product information. The HTTP method corresponding to the creation of a resource is POST
. A POST
request’s parameters are usually passed in the request body, so the product information parameter goes there. Once the product resource is created, the action should return the newly created resource identified by its path: /products/{addedProductId}
.
Now, what is the HTTP representation of the search action of the catalog resource? The HTTP representation of search for products in catalog using a free query is GET
/products?free-query={free query}
, as shown in figure 3.16.
When we search for products, we want to retrieve them, so we have to use the GET
HTTP method on the /products
path. To only retrieve products matching some query, like a product name or partial description, we need to pass a parameter to this request. A GET
HTTP request’s parameters are usually provided as query parameters in the path, as demonstrated in the following listing.
GET /example?param1=value1¶m2=value2
GET /products?free-query=something
The parameters are located after the ?
at the end of the path and provided in name=value
format (param1=value1
, for example). Multiple query parameters are separated by &
. Once the search is done, the GET
request returns the resources matching the path (which includes the free-query
parameter).
We have represented all of the catalog resource’s actions, so let’s work on the product resource. We start with get product, which is relatively easy to represent as an HTTP request, as illustrated in figure 3.17.
We want to retrieve the product resource identified by /products/{productId}
, so we again use the GET
HTTP method. The HTTP representation is therefore GET
/products/{productId}
. A GET
resource always returns the resource corresponding to the provided path, so this action returns the content of this resource.
Now it’s time to discover new HTTP methods! How do we represent delete product with the HTTP protocol? The HTTP representation of this goal is simply DELETE
/products/{productId}
, as shown in figure 3.18.
The DELETE
HTTP method’s purpose is obviously to delete the resource matching the provided path. In our use case, this action does not return any information.
Deleting a product was easy. Now can you guess what HTTP method we will use to update a product? There’s a trap here—the HTTP representation of update product is PATCH /products/{productId}
, not UPDATE /products/{productId}
, as shown in figure 3.19.
The PATCH
HTTP method can be used to partially update a resource. Like POST
, the request parameters are passed in the request’s body. For example, if you want to update a product’s price, you can use PATCH
on the product resource and pass the updated price in the body. In our use case, this action does not return any information.
Our last example illustrates an HTTP method that has two purposes. The HTTP representation of replace product is PUT /products/{productId}
, as illustrated in figure 3.20.
The PUT
HTTP method can be used to totally replace an existing resource or to create a nonexisting one and provide its identifier. In the latter case, it has the same effect as the add product to catalog action. Like POST
, the request parameters are passed in the request’s body. In our use case, this action does not return information, but if you use PUT
for creating a resource, the created resource should be returned.
So, the POST
, GET
, PUT
, PATCH
, and DELETE
HTTP methods essentially map the basic CRUD functions (create, read, update, delete). Do not forget that these actions are made from the consumer’s perspective; for example, if you DELETE /orders/O123
, it does not mean that the order O123
will actually be deleted from the database containing the orders. Such actions might simply update this order status to CANCELED
.
These CRUD HTTP methods also have to be used to represent more than or not so CRUD actions. It can sometimes be difficult for beginning REST API designers (and sometimes even seasoned ones) to choose which HTTP method matches an action that doesn’t obviously map to a CRUD function. Table 3.1 shows some examples of actions that can help you see beyond CRUD.
Table 3.1 HTTP methods beyond CRUD
HTTP method | Action |
POST (and PUT in creation) | Create a customer, add a meal to a menu, order goods, start a timer, save a blog post, send a message to customer service, subscribe to a service, sign a contract, open a bank account, upload a photo, share a status on a social network, and so on |
GET | Read a customer, search for a French restaurant, find new friends, retrieve opened accounts for the last 3 months, download a signed contract, filter best selling books, select black-and-white photos, list friends, and so forth |
PATCH/PUT | Update a customer, replace goods in an order, switch plane seat, edit an order’s delivery method, change an order’s currency, modify a debit card limit, temporarily block a credit card, and so on |
DELETE | Delete a customer, cancel an order, close a case, terminate a process, stop a timer, and so on |
If you really cannot find a resource and HTTP method pair to represent your action, you can use the default POST
HTTP method as a last resort. We will talk more about this in section 3.4.
Congratulations! You have learned to transpose API goals into REST resources and actions, and represent them using the HTTP protocol. You should now have a good overall view of REST API resources and actions. Let’s sum up everything you’ve learned thus far with a cheat sheet, shown in figure 3.21. That makes a lot of things easier to remember!
Remember, at the beginning of this chapter, you saw that the result of GET /products/P123
was some data. We now have to design that data!
You now know how to transpose API goals into REST resources and actions and give them a programmable representation with paths and methods using the HTTP protocol. You have also identified the actions' parameters and returns. But the resources, parameters, and returns you have identified are only vaguely described. How do we design these data items? The design process is outlined in figure 3.22.
Whatever the type of API, we start designing the data just like any programmable representation of a concept—just like a database table, a structure, or an object. We simply list the properties and stay consumer-oriented. Consumers must be able to understand the data, and we must not expose inner workings through its design. Once we’ve designed the core concepts, we can design the parameters and responses by adapting them. And finally, we must ensure that consumers will be able to provide all the data required to use the API.
As before, the simplest method shown here is intended to expose the basic concepts. Feel free to adapt it or use a different software design method that you’re familiar with, as long as you keep the spirit alive and achieve the same results.
The concepts that we have identified and turned into REST resources will be exchanged through parameters and responses between the consumer and provider. Whatever its purpose, we must take care in the design of such a data structure to offer a consumer-oriented API, just as we did when designing the API goals. Figure 3.23 illustrates how to design a concept such as a product.
We start by listing the data structure’s properties and giving each a name. A product can have reference
, name
, and price
properties, for example. It could also be useful to tell the customer when the product was added to the catalog (dateAdded
) and let them know whether it’s in stock or not (unavailable
). And what about listing the warehouses where this product can be found and its suppliers? Finally, we might want to return a fuller description of the product.
While listing these properties, you must remember what you learned in chapter 2 about the consumer’s and provider’s perspectives. We must analyze each one to ensure that our design is focused on the consumer’s perspective and does not reflect the provider’s. We can do this by asking ourselves if each property can be understood, is really the consumer’s business, and is actually useful, as shown in figure 3.23. In our example, the property names seem understandable; we have avoided obviously cryptic names such as r
and p
for reference and price, for example. But on second thought, the warehouses
list isn’t really relevant for users, so we’ll remove that. We’ll also rename the unavailable
property to definitelyOutOfStock
to be more explicit.
The most important information about a property is its name. The more self-explanatory the name is, the better. But defining a property only by its name isn’t enough when it comes to describing a programming interface, as shown in figure 3.24.
We also need to be clear about each property’s type. For example, what is the type of the reference
property? Is it a number
or a string
? In this use case, it’s a string
. Is it an essential property that must always requested or returned? That is the case for this one. And a final question: What exactly is a reference? Its description indicates that it is a unique ID identifying the product.
As figure 3.24 illustrates, for each property, the characteristics we need to gather are
Figure 3.25 shows a detailed list of the possible properties of the product resource. It also shows on the right of the properties list an example of a product’s JSON document.
So, a product is composed of required and optional properties. Some of them (such as reference
, price
, definitelyOutOfStock
, and dateAdded
) are of basic types (such as string
, number
, boolean
, or date
). There can also be complex properties, such as supplier
, which is an object. (A property can also be an object
containing properties or an array
.)
A name and a type are the most obvious information to gather about a property when designing a programming interface. An API can be consumed by software written in many different languages.
In addition to knowing a property’s name and type, we must also know if this property should always be present. Indicating whether a property is required or optional is an often-forgotten aspect of API design, but this information is crucial in both the parameter and response contexts for API designers, consumers, and implementers. Note that the required or optional status of a property can vary depending on the context. For now, we’ll set this status to required only if it is an essential property of the concept.
Sometimes, name and type are not sufficient to accurately describe a property. To provide additional information that cannot be obviously reflected by the property’s name and type, adding a description can be valuable in such cases. Once we know what our concepts are made of, we can use them as responses or parameters to our goals.
The same concept can appear in different contexts, as shown in figure 3.26.
For example, the catalog resource actions add product and search for products both return a product (or, in the latter case, potentially more than one). The product resource action get product returns a product too. These different product representations might not be exactly the same as shown in figure 3.27.
While add product and get product should return the complete product, search for products can only return a reference
, name
, price
, and the supplier’s name as supplierName
.
When we design responses, we should not blindly map the manipulated resource. We must adapt them to the context by removing or renaming properties and also adjust the data structure. But do we design the parameters the same way?
When we add, update, or replace a product, we pass some product information as a parameter (figure 3.28).
But what does this parameter consist of? Or, more precisely, these parameters as shown in figure 3.29. The product information parameter passed in these three cases might not be the same; they may not look like the responses we just designed, and they may not exactly reflect our product concept.
When a product is created, its reference is generated by the backend, so there’s no need for the consumer to provide it when adding a product. But we need this reference to update or replace a product (note that the reference will be passed in the path as a path parameter: /products/{reference}
). In all these use cases, the consumer does not need to provide the supplier.name
property; only its reference is needed; the backend has a way to find a supplier’s name based on its reference. To simplify the data organization, we can therefore remove the supplier
object and replace it with supplierReference
. dateAdded
is also generated by the backend when the product is added to the catalog, so we don’t need that either. As with responses, the same concept can have different representations in an API’s parameters, depending on the context (creation versus updating, for example).
By proceeding this way, we ensure that the product information parameter contains only the data that’s absolutely necessary in each context. But are we sure the consumer can provide all this data?
When adding a product to the catalog, consumers should be able to easily provide data such as name
, price
, and description
. But what about the supplierReference
? How can consumers know such a reference? This kind of questioning probably sounds familiar. That’s because when identifying the API goals, we verified that consumers could provide all the necessary inputs, either because they already know the information or because they are able to retrieve it from another API goal. But now we are dealing with a more detailed view of these input parameters.
Consumers must be able to provide all of a parameter’s data, either because they know the information themselves or because they can retrieve it from the API. If data cannot be provided, it might be a sign of a missing goal or the provider’s perspective. Therefore, we must verify again that all the needed data can be provided by the consumer. This verification process, illustrated in figure 3.30, ensures that consumers will always be able to provide parameter data, and that there are no gaps in the API.
In this case, consumers might already know the supplierReference
because it is provided on the product’s label, or it may come from a response we have already designed. It could also come from another goal; in which case, we just need to update its response to add this information in order to provide it to the consumer. We may also simply have missed a goal. In that case, we will have to add it to the API goals canvas and process it like any other goal, identifying who can use it, defining its inputs and outputs, and designing its programmable representation. Or we might simply realize that we don’t really need it.
We will not solve this mystery here—its purpose is to show that parameters must be closely verified and that you can discover some missing features when you get into the details of an API’s design. This is totally normal; designing an API is a process of continuing improvement. Step-by-step, the design will be refined to become more accurate, complete, and efficient.
What about the free-query
parameter of the search for products goal? It is a string that can contain a partial description, a name, or a reference. This is an optional query parameter; if it is not provided, all available products are returned.
Whatever the parameter is—a query parameter like free-query
or even a body parameter not based on an identified concept—we do the same thing. We choose a consumer-oriented representation and check that the consumer is able to provide any requested data.
With what you’ve learned so far, you should be able to design any basic REST API. But sometimes you might encounter challenging design problems and sticking to the chosen API type representation might be hard. Perfection is not always possible in the API design world.
When you choose to use a specific API type, it is important to know that sometimes you can encounter limitations. You might struggle to find a representation of a goal that conforms to the chosen API model. You might also end up with a representation that conforms to the model but is not as user-friendly as you expected. Sometimes the perfect representation does not exist, and therefore, as an API designer, you will have to make some trade-offs.
Mapping actions on resources to HTTP methods and paths is not always straightforward. There are common techniques to circumvent such problems, and often by exploring various solutions, you can finally find one that works with your chosen API model. But sometimes this API style-compliant solution might not be user-friendly.
You might have noticed that I carefully avoided transposing goals that were related to a user buying products and focused on the catalog-related goals. Goals related to catalog management are perfect to show the basics of HTTP and REST APIs. The resources are relatively simple to identify, and goals with actions such as add, get or search, update, and delete (who said CRUD?) are easily mapped to HTTP methods. But things aren’t always so simple.
When users buy products, they have the check out cart goal at the end of the process. How can we represent such a goal when it’s not obvious how to transpose it into a path and HTTP method couple? When a designer fails to map an action on a REST resource to any HTTP method, a first option is often to create an action resource (figure 3.31).
An action resource is not a thing represented by a noun, but an action represented by a verb. It is simply a function. An action resource can be seen as a method of a class and, therefore, can be represented as a sub-resource of a resource. If we choose to represent the cart resource by the /cart
path, the cart.checkout()
method could be represented by the path /cart/check-out
. But we could also consider it as a standalone checkoutCart()
function and create a /check-out-cart
action resource accordingly. In both cases, we use the HTTP method POST
to trigger the action resource.
An action resource absolutely does not conform to the REST model, but it works and is totally understandable by consumers. Let’s see if we can find a solution that’s closer to the REST model. We could, for example, consider that checking out the cart changes some status (figure 3.32).
The cart resource might contain a status
property. To check out the cart, we can update it with PATCH
to set its value to CHECKING_OUT
. This solution is closer to the REST model but less user-friendly than the action resource: the check out cart goal is hidden within an update of the cart resource. If we keep on brainstorming, I’m sure we can find a solution that totally fits the REST API model.
Let’s get back to the basics. We must ask ourselves a few questions:
Well, an order is created that contains all the cart’s products. And after that the cart is emptied. That’s it! We are creating an order. Therefore, we can use a POST /orders
request to create an order and check out cart, as shown in figure 3.33.
This solution totally conforms to the REST API model, but is it really user-friendly? The purpose of this REST representation might not be obvious to all users.
So which option wins? The totally non-REST but so user-friendly POST /cart/check-out
or POST /check-out-cart
action resources? The more REST but a little bit awkward PATCH /cart
? Or the totally REST but not so user-friendly POST /orders
? It will be up to you to choose (figure 3.34) or to find a better solution.
Sometimes you might not find the perfect API goal representation, even after intense brainstorming and with the help of the entire team. Sometimes you might not be really satisfied or even be a bit disappointed by an API design you are working on. Unfortunately, this is totally normal.
It is important to master the fundamentals behind the chosen programming interface model or API style to be able to find solutions that are as close as possible to the chosen model. But it is also important to be able to make some reasonable trade-offs to keep the API consumer-friendly and not diverge too much from the API model. Skill at this comes through practicing, observing other APIs, and, most importantly, talking to your consumers and other API designers.
Congratulations! You should now be able to transpose any API goal to a REST API. But now that we have covered what a REST API is and how to create one based on an API goals canvas, we should explore REST beyond mapping goals to HTTP method and path pairs. This is important because REST matters for the design of any type of API.
As T.S. Eliot said, “The journey, Not the destination matters….” I could have explained all the API design principles presented in this book using the totally outdated Xerox Courier RPC model that was created in the 1970s, the despised SOAP model created at the end of the 20th century, the now widely adopted REST, or the more recent gRPC or GraphQL. And these are only a few examples among many. As explained in chapter 1 (section 1.3), there have been, there are now, and there always will be different styles of programming interfaces allowing software to communicate remotely. Each of them had, has, or will have its own specificities, pros, and cons, and will obviously produce an API with a specific look and feel. But whatever its type, designing an API requires basically the same mindset.
Up to this point, we’ve been considering REST APIs as APIs that map goals to paths and HTTP methods. But REST is far more than that. REST is a widely adopted API style; but, more importantly, it is based on the solid foundations of the REST architectural style, which is crucial to know when creating any type of API. That’s why I chose REST as the main example programming interface for this book. Let’s see what this REST style is, and what it means not only for API designers but also for API providers.
When you type a URL such as http://apihandyman.io/about in a web browser’s address bar, it sends a GET /about
request to the apihandyman.io web server. It’s easy to imagine that the web server will return some static HTML document stored in its filesystem, but that might not be the case. The /about
resource’s content could be stored in a database. And what happens when a social media web server receives a POST /photos
request? Does the server actually store the provided file as a document in a /photos
location on the server’s filesystem? Maybe. Maybe not. It could also store this image in a database.
Browsers interacting with web servers are left totally unaware of such implementation details. They only see the HTTP interface, which is only an abstraction of what it can do and not an indication of how it is done by the server. And how is it that a web browser can interact with any web server implementing an HTTP interface? It’s because all web servers use exactly the same interface.
This is a part of the magic of HTTP. This is a part of the magic of REST.
The REST architectural style was introduced by Roy Fielding in 2000 in his PhD dissertation “Architectural Styles and the Design of Network-based Software Architectures.” Fielding developed this architectural style while he was working on version 1.1 of the HTTP protocol. During the HTTP 1.1 standardization process, he had to explain everything—from abstract web notions to HTTP syntax details—to hundreds of developers. That work led to the creation of the REST model.
The aim of the REST architectural style is to facilitate building distributed systems that are efficient, scalable, and reliable. A distributed system is composed of pieces of software located on different computers that work together and communicate through a network, like the one shown in figure 3.35.
This should sound familiar to you because, from the beginning of this book, we have talked about distributed systems. A web browser and a web server comprise such a system, as do a consumer (like a mobile application) and API servers. Such systems must provide for fast network communication and request processing (efficiency), be capable of handling more and more requests (scalability), and be resistant to failure (reliability). The REST architectural style also aims to facilitate portability of components (reusability), simplicity, and modifiability. To achieve all this—to be RESTful—a software architecture needs to conform to the six following constraints:
That might sound terribly scary and far from API design concerns, but these constraints should be understood by any API provider, in general, and any API designer, in particular.
The REST architectural style was primarily created as a support to describe the World Wide Web and the HTTP protocol, but it can be applied to any other software architecture design with the same needs. A REST API, or RESTful API, is an API (which, in a broad sense, comprises both the interface and its implementation) that conforms (or at least tries to conform) to the REST architectural style constraints. These constraints obviously have a lot of implications for REST APIs but also for any type of API. Some of them can be a little hard to grasp at this time, but we will explore them throughout the book while digging into the various aspects of API design. What if I were to tell you that we’ve already started to explore three of them, perhaps without you realizing it?
Do you remember the consumer’s perspective we talked about in the previous chapter? As shown in figure 3.36, there are two REST constraints underneath this design principle.
When exploring the consumer’s perspective, we saw that an API provider must not delegate its job to the API consumer—like turning the magneton on and off on the Kitchen Radar 3000 (see section 2.1). This is an example of the client/server constraint.
We also saw that an API consumer is only aware of the provider’s API and does not know what’s happening beyond this interface—like in the restaurant example where customers order meals without having a clue about what is really happening in the kitchen (see section 1.2.2). This is what the layered system constraint means. These two constraints, and focusing on the consumer’s perspective in general, will help you build APIs that are easy to understand, use, reuse, and evolve.
We also have started to uncover the uniform interface constraint in section 3.2.3 and section 3.2.4 as illustrated by figure 3.37.
Each resource is identified by a unique path. Inside a single API and across APIs, POST /resource-A
and POST /resource-B
have the same meaning: create something. By representing goals with HTTP using unique paths and, most importantly, standardized HTTP methods, we are creating a uniform interface, which is consistent with itself and also with other interfaces. In chapter 6, we will get deeper into the other aspects of the uniform interface. We will talk more about REST representation and discover the other constraints (statelessness, cacheability, and code on demand, for example) while learning to design APIs in the next chapters. Table 3.2 gives a recap of all sections describing the REST architectural style constraints.
Table 3.2 REST constraints in The Design of Web APIs
REST constraint | Insights in the book |
Client/server separation constraint | Chapter 2, section 2.1 |
Statelessness constraint | Chapter 5, section 5.3.4 |
Cacheability constraint | Chapter 10, section 10.2.2 |
Layered system constraint | Chapter 1, section 1.2.2 |
Code on demand constraint | Chapter 5, section 5.3.2 |
Uniform interface constraint | This chapter, section 3.2.3 and section 3.2.4, and chapter 6 |
In the next chapter, you will discover a structured way of describing programming interfaces, much like the one we have designed, by discovering why and how to describe an API using an API description format.
object
, array
, string
, number
, date
, or boolean
types when designing data.