Table of Contents
After three years of drafting, one year of writing, four years of revising, and two years of editing, I have finally completed my blog – REST Level: Best.
In this article, we’ll focus on REST API development and write down a set of tips that will, if implemented correctly, make our REST APIs explained, easy to use, consistent and intuitive.
When discussing the APIs, it’s important to remember that they are not only technical interfaces for software. By creating well-designed APIs we are practicing the Art of Communication. Whether we are building internal, for components within our organization, or public ones, for third-party use, our REST APIs will sooner or later be consumed by other developers. That’s why they need to be simple and accessible, but at the same time stable and standardized.
A tool that could help us evaluate the quality of RESTful API design is Richardson Maturity Model. In short, The RMM was proposed by Leonard Richardson, and it promotes three levels of API maturity, based on its support for Resources, HTTP Verbs, and Hypermedia Controls. It is useful for learning about the concepts of REST, but we will combine it with a set of design tips that will help our APIs achieve the highest maturity levels of the model.
So, here it goes – Richardson’s levels and 31 tips for designing the REST API, or better said a REST API how to.
Level 0 of REST API development – The Swamp of POX (Plain Old XML)
If they tell you all the APIs are beautiful, don’t trust them. Or that the real beauty of the REST API is on the inside. That’s just something that developers of the ugly API say. Even the name suggests we won’t find anything RESTful here.
Ok, maybe I’m being too harsh. The point is, if our REST APIs are at Level 0, we are not using the full capabilities of the Web. In this approach, HTTP is treated only as a transport protocol for RPCs (Remote Procedure Calls). Here, we’ll usually find services with a single Uniform Resource Identifier (URI), using a single HTTP method (typically POST, used both for retrieving and creating the data), and the buttload of JSON (or XML) in the request body. The body itself determines what kind of operation needs to be performed. Sometimes it is well structured and straightforward, other times, our Adapter will overheat from trying to process 100 kilos of JSON into a meaningful operation.
Although Level 0 is not considered RESTful at all, it is a good place to start with our first set of tips, mostly concerned with URIs.
Tip #1: Just in case, lowercase
As a reminder, RFC 3986 defines a syntax of a generic URI as
scheme “://” authority “/” path [ “?” query ] [ “#” fragment ]
The same document specifies URIs as case-sensitive except for the scheme and host components.
So, https://atomskimarketing.com/recipes/desserts/tiramisu and https://atomskimarketing.com/RECIPES/Desserts/Tiramisu are not the same, whereas URIs https://atomskimarketing.com/recipes/desserts/tiramisu and HTTPS://ATOMSKIMARKETING.COM/recipes/desserts/tiramisu are the same.
To avoid misunderstanding, we should give preference to lowercase letters.
Tip #2: Hyphens, not underscores
Also, our URIs cannot contain space literals, so we need an alternative.
We could encode it using the percent-encoding (No God, please no!) and get something like this: https://atomskimarketing.com/desserts/italian%20tiramisu
Another option would be using the underscore (_) character. However, when URIs are pasted into browsers or editors, they are often underlined and the user can click on them. This underlining could then hide the underscore itself.
To improve the readability of URIs and make them easy for users to scan and interpret, we should use hyphens (-) instead.
Tip #3: No extensions, please
Back in the day, web servers simply mapped URIs directly to files. This is no longer the case, so instead of using dots in the URI to represent file extensions, the Content-Type header and corresponding media type should be utilized to indicate the format of a message’s body.
OK, so far so good. What’s next? Can we somehow make our APIs stand out? Can we make them more web-friendly? Can we turn a Swamp into a Pond? Sure we can. With the help of resources.
Level 1 – REST APIs explained: Resources
Level 1 introduces the concept of resources. A resource is an abstract data structure, similar to objects in the object-oriented programming paradigm. It is an entity that can be identified, named, and manipulated. REST APIs should use URIs to represent their resources, becoming more organized that way.
Instead of having all of our requests transmitted to a single URI, we now communicate with multiple different endpoints, each representing individual logical resources.
Here we define our next tip:
Tip #4: Slashing the resource paves the REST API path forward
To move through a jungle, we typically use a machete. We cut through thick vegetation, branches, and vines, and create a pathway. The machete’s sharp blade allows us to efficiently chop the dense vegetation, making it easier to move forward.
The same goes for the jungle of resources. We use the forward slash separator (/) in the URI to chop the resources and adjust the granularity. With it, we indicate a hierarchical relationship between resources. Fine-grained URIs give flexibility and, in return, satisfy the specific needs of a large number of business functions or use cases.
A point to remember – two different URIs map to two different resources. So, no trailing forward slashes. They have no semantic value and may create confusion. We should always clean up after ourselves, we don’t have a butler.
That’s all good, but where to cut? How to split and model the URI, indicating the unique resources within our API? The process is actually quite similar to object-oriented modeling (OOM) or data modeling (for a relational database scheme). It should help to first define the basic building blocks – Resource archetypes.
There are four unique archetypes: document, collection, store, and controller. Whether or not we should use all of them depends on the nature of our REST APIs.
The Document archetype typically contains fields with values that describe a resource. Once we reach Level 3, we will see that it also contains links to other related resources. Document archetype is often compared to an object instance or database record, and may have child resources representing subconcepts.
A Collection resource, on the other hand, is a server-managed list of document resources. Since the server is in charge of it, it’s not up to the client to decide whether a new resource can be created. However, if allowed, a client can add new resources to the collections.
Similar to a Collection, there is also a Store archetype. This one is managed by the user, so a client can determine what gets added, removed, or modified.
The final one is the Controller resource, which models a procedural concept.
Application-specific actions, that can’t be represented with standard methods, are typically mapped to a Controller resource. Controllers should be used as a last resort, and we should always try to use a Collection or Document resource to describe the intent of the action.
Tip #5: Avoid overexposure
When modeling our REST API, we have to make sure we are not unintentionally exposing internal business logic. And of course, we should never expose our Domain entities in the API. Unless we want to couple our API clients to the internal entity structure, and then break them by changing that same structure. That would be weird.
Tip #6: The path of least (brain) resistance
We need to keep the base URI simple and intuitive. There should be only two base URIs per resource – the first URI is for a collection, and the second is for a specific element in the collection:
Tip #7: Truth is singular and perceptions plural
For URI representing a document resource, a singular noun or noun phrase should be used. For collections and stores, we should use plural nouns or noun phrases.
Tip #8: My productivity is overwhelming! – J(a)son Statham
If we want to enhance developers’ productivity, our REST APIs should use JSON for resource representation. Just like Jason Statham’s physique, our JSON should be well-formed. Furthermore, using JSON will allow our resources to be extended with new properties. In such cases, if possible, we need to make them optional, to maintain backward compatibility within different API versions.
Tip #9: Wear Sunscreen (with SPF factor)
We should protect our REST endpoints with some SPF factor. Sorting, Pagination, and Filtering are important aspects of API design.
Our clients will often try to fetch a large amount of data. Same as when we are buying on some webshop, we will look for a way to lower the (network) shipping costs. Those three techniques will reduce data loading time, make responses easier to handle, enable smoother UX, and reduce network traffic.
The way to support Filtering, Pagination, and Sorting is to utilize the query component of a URI:
- GET /desserts?type=cake&seller-id=1234
- GET /desserts?page=5&pageSize=25
- GET /desserts?sort=asc(type)
Whether we are using sort or sort-by URI parameter, combined with field name for sorting, or offset and limit or pageSize and page for pagination, it’s up to us to define, but we need to be consistent across our API(s).
With sunscreen, we have covered Level 1 topics. However, we are only halfway there. It’s time to level up.
Level 2 of our REST API how to: HTTP Verbs
Introducing the HTTP Verbs to our API is considered a small step for us developers, but one giant (wise) leap towards the final Glory of Rest.
Level 2 APIs diverge from using a single HTTP Verb for all interactions. Here we get to extend our vocabulary and leverage the standard HTTP Verbs to indicate the action we want to perform on a resource. It is similar to designing a class in the OOP. We associate a variety of methods with objects or classes that define the behavior and actions that can be performed.
Tip #10: Verb-bidden path
URI should uniquely identify resources, and not indicate that an action is executed. Using verbs in URIs is forbidden for Level 2 services. The path contains only nouns. Different HTTP methods invoked on the same URI provide different functionality on the same resource.
Tip #11: Choose your verbs wisely
Before considering HTTP methods, it is important to mention their characteristics, such as safety and idempotency.
Idempotent methods retain the same outcome even when executed multiple times. In mathematics, for example, multiplying any number by zero will always result in zero, regardless of how many times the operation is repeated. Similarly, in our REST APIs, the impact on the server when making a single request is equivalent to the impact of making multiple identical requests.
Safe methods, on the other hand, are always read-only operations. They preserve the resource’s state and do not produce side effects.
Tip #12: Get rid of the body
The GET method retrieves a representation of the specified resource. It does so without modifying the resource itself. No matter how many times we request the same URI, we should get the same result. It is safe and idempotent. Always remember, if you want to get away with murder, get rid of the body. A client’s GET request should have no body part.
Tip #13: HEAD in the right direction
The HEAD method retrieves headers of the resource. A response is similar to a GET request but without the response body. This method is typically used to check whether a resource exists or to read its metadata.
Tip #14: Use Post-it notes
Modern interpretations of the Bible say that God used the POST method to create Adam. Hence, to create a new resource, the same method must be used in our REST APIs.
The body of the request contains the suggested state representation of the new resource. Since those requests are modifying the server-owned collection, they are not guaranteed to be idempotent. The POST method is also used for the controller’s execution.
Tip #15: PUT – Perfect Updating Technique
The PUT method updates mutable resources. It replaces all current representations of the stored resource with the new representation provided in the request. If the resource does not exist, PUT may create it.
Tip #16: PATCH Properly attached
Born in 2010, and added to the HTTP protocol, this method solved the problem of her older siblings – PUT and POST methods. They require using a full representation of the resource, whereas PATCH applies partial modifications to a resource, updating only fields provided in the request. Every other field of the resource remains unchanged on the server side.
Tip #17: When in doubt, delete it
It may come as a surprise, but the DELETE method deletes the specified resource. Once processed, the resource identified by the given URI can no longer be found by clients. This however doesn’t always mean the resource is physically deleted. Its state can still be maintained internally (e.g. “soft” delete), but it is no longer accessible. We don’t suggest deleting the resources that are causing some troubles in production, but it’s always an option.
Tip #18: What are our options?
Speaking about options, there is an HTTP Verb with the same name. The OPTIONS method describes the communication actions available for the target resource. It allows the client to query information about ways to interact with a resource.
Tip #19: Don’t be a mole
Don’t dig tunnels. Don’t encode the information within the URI and then transmit it using the POST or GET method. This mechanism is called URI tunneling, and although it can sometimes look very convenient, especially for executing non-CRUD operations, it is not web-friendly. It doesn’t describe the way to operate the resource and also violates the principles of Web architecture. So, don’t be a mole. Or a rat. Snitches get stitches.
Please keep in mind that the four basic HTTP methods (POST, GET, PUT, DELETE) are often called the CRUD methods because they are commonly used in a very relational database-oriented way. However, this database approach isn’t the way the authors of the HTTP specification thought about the HTTP methods.
Besides HTTP Verbs, Level 2 services use status codes. It’s a valuable mechanism for delivering the results of client requests. Together with Verbs, they provide a general framework for operating on resources over the network. HTTP defines more than fifty standard status codes divided into five categories. Let’s list them below:
Tip #20: Use 1xx codes for…
Codes starting with a digit 1 indicate that the server has received the request and is still in progress. Sooner or later a client will receive a final response to the request.
…protocol-level information. There, a response finally arrived. 1xx should be used for informational purposes.
Tip #21: Live up 2(xx) success
2xx status codes signal that the request was successfully received, understood, and processed. We use them to indicate that everything went well on the server side.
Tip #22: 3xx – Take a detour
When a resource is moved to a new location, status codes starting with digit 3 should be returned. A redirect means that the client’s request was received successfully, but that the resource was found elsewhere. Be kind enough to provide clients with information about where to find the content they’re looking for. We don’t want to be like those Supermarkets, changing the layout of our store and forcing the customers to spend more time looking for the items.
Tip #23: Congratulations 4(xx) your excellent work, client
Our API clients hate these. 4xx status codes denote an error on their side. Whether it’s a matter of a bad request or trying to access an unauthorized area, these are problematic ones. The REST APIs should respond with the appropriate error code and additional information, and not just point fingers at the clients.
Tip #24: 5xx – Aaah, it can happen to anyone
Well, well, well, how the turntables… 500-level status codes indicate the problem is on the server’s side. The request is valid, so we should check on our end why we cannot process it.
Tip #25: What’s your 20?
Police, military, and emergency service all utilize codes for efficient and concise communication. Just like transmitting the wrong code over the radio can have serious consequences, it is crucial to use HTTP response status codes accurately.
Here are the most used status codes, starting with the one that marks the error on the server side:
500 Internal Server Error – This is the generic REST API error response, which indicates service malfunction. Typically used to hide all those nasty exceptions blowing up in our applications.
Next, some client error codes:
409 Conflict – Marks a violation of resource state. This happens if, for example, a client tries to delete a resource that is currently being updated by another user.
404 Not Found – One of the most famous status codes. It occurs when a user is trying to access a resource that does not exist and REST API can’t map the client’s URI to a resource.
403 Forbidden – Indicates that access to a specific resource is forbidden, i.e. the client is not allowed to view the content. REST APIs use 403 to enforce application-level permissions.
401 Unauthorized – Used when there is a problem with the client’s credentials. Credentials may be incorrect or not provided at all.
400 Bad Request – The generic client-side error status. It should be used when no other 4xx error code is appropriate.
A pinch of redirection:
304 Not Modified – If the resource a client is trying to retrieve has not been modified since the last time client accessed it, 304 should be returned. It is used to preserve the bandwidth.
204 No Content – The request was successfully processed, but REST API is not returning any content, i.e. response body is intentionally empty.
201 Created – Used to indicate that the request has been fulfilled and the resource was successfully created.
And, we’ve saved the best for last:
200 OK – One of the theories regarding the origins of the word OK is one that includes
The Civil War. A battalion returning from the front would carry a sign displaying the number of soldiers killed in action – “14 Killed”, “6 Killed”, and so on. If there were no casualties, the sign reads “0 K.”
If the client’s action was successful and there is no specific code in the 2xx series more appropriate to use, our REST API should return 200, basically saying that nobody was killed by the client’s request.
Tip #26: Don’t lie
A few years ago, at the Oscar Awards, a wrong film was mistakenly announced for the best picture. “And the Academy Award for Best Picture goes to… ‘La La Land’! Oh no, wait a minute. The winner is actually ‘Moonlight’!”
What do you think, how did the members of the first film crew feel? We’ll tell you how – the same way we feel when we receive a 200 (“OK”) status with errors communicated in the response. Don’t lie. Don’t confuse your clients – make proper use of the HTTP response status codes.
Tip #27: Learn to wrap gifts
HTTP status codes, even the accurate ones, are often insufficient when it comes to troubleshooting problems. To make things easier, it is essential to send additional information in the response. If the client’s request fails due to an error in the request itself, we need to provide the explicit error message (how else would they learn!?). Additionally, if the request was valid, but encounters unexpected exceptions in the business logic, it is best to shield the client from these details and wrap them into something more pleasing. Since the consumers of our API are not only machines but also humans, it is important to ensure that everyone can easily interpret the error messages.
Consistent error representation is necessary. The response message should include the error response in the message body, with systematically organized and categorized errors. We should use JSON format, containing machine-readable error codes and human-readable error descriptions.
"errorDescription": "Request does not contain the mandatory parameter - providerId”
Tip #28: Use your head(ers)
HTTP headers are components of the HTTP that let the client and the server exchange additional information with each request or response. HTTP specifications have a set of standard headers, colon-separated name/value pairs, through which we can send extra bits of information along with our message. Below, we will list some of them:
The Content-Type header indicates the media type of the data found in the request or response. Clients and servers rely on this value to appropriately handle a sequence of bytes in a message’s body.
The Content-Length header gives the size of the entity-body (payload) being sent from the server to the client. It specifies the length of the content in bytes, which is useful for efficient data transmission and error detection.
The Location header must be returned when creating a resource using the POST HTTP method. Its value is a URI that identifies a newly created resource.
This header is typically used to send authentication credentials from the client to the server. The server then verifies the client’s identity and allows access to protected resources.
We can optionally use custom headers, but only to provide additional information. The previous recommendation to prefix such headers with “X-” is now deprecated. Sorry X-files fans.
Tip #29: Be the best version of yourself
There is nothing permanent except REST API change, said Heraclitus once.
The man lived in 500 BC and he know the deal back then – Even a great REST API design must be adaptable to the evolution of our product or business and will change eventually. And he also said: “Never release an API without a version”, or there is a chance he did. So, why don’t we version our APIs then? Perhaps we just need to select the method that suits us best.
There are a few ways to version the REST APIs:
Versioning through the URI path
The version can be a part of the URI, for example:
If we decide to version through the URI path, there are some things we need to keep in mind:
We should use simple ordinal numbers like 1, 2, etc. prefixed with the “v” and placed at the beginning of the URI. The dot notation like v1.2 should be avoided (implies a granularity of Versioning).
URI path Versioning is commonly used due to its simplicity. However, as we’ve mentioned earlier, each character in a resource’s URI affects its identity. Including the API version indicators in the URI, such as “v1” or “v2”, implies multiple versions of the resource, which is often unintended.
Versioning through query parameters
Another approach to Versioning an API is to make it part of the request parameters.
Although it is a simple method, it can make the API less intuitive and introduce the complexity of the API implementation, due to a high variability of query parameters and their types.
Versioning through HTTP headers
We can version our API through custom headers, or the Accept/Content-Type header (Accept: application/json; version=1).
Versioning through HTTP headers enable clients to use the same URIs, regardless of any version upgrades. It is less visible and reduces the size of the URI.
Level 3 of REST API development: Hypermedia Controls
The final stop towards reaching the REST heaven leads through unpronounceable HATEOAS. It sounds like “He ate oats” or “They hate us”. I’ll stick with the former since it stands for Hypermedia as the Engine of the Application State.
What the f…fabulous concept. In any case, Hypermedia Controls allow a client to navigate through the REST API without hardcoding URIs to specific resources.
All the resources are linked and even though the client may not know all resource endpoints upfront, by having access to the root resource, it can find all the needed information. Self-documentation and discoverabi lity are the keywords of the Level 3 REST APIs.
Server responses will now contain URI links to other resources and all the needed metadata (available actions). This is the core principle of hypermedia – the ability to modify the state of an application by traversing links connecting various resources. It’s like solving cast puzzles, where each step or, in our case, an application state transition reveals new possibilities to the client.
Tip #30: Listen to Linking Part
As mentioned earlier, links play a crucial role in providing navigation and discoverability within RESTful APIs.
There’s no absolute standard for representing Hypermedia Controls, so it is important to have a consistent form for representing links and link relations.
When referencing another resource, links are usually added by using the href (which stands for “hypertext reference”) attribute. It identifies the target resource and appropriate actions.
Each identifiable resource should also provide a self-linking representation in a response message body. It provides a way for clients to reference and understand their current context within the REST API.
HTTP/1.1 200 OK
HTTP/1.1 200 OK
Tip #31: Save everyone’s time
Document the REST API properly.
Let me rephrase that – Document all APIs properly.
This tip is not specific to Level 3 services, but it is worth emphasizing. Writing good documentation helps other developers integrate their applications with our system easily.
As we design and build our APIs, we also need to provide the learning materials. Answering the “How does this work” question is a good start, but if we really want to take care of our clients, we also need to provide context and explain how to do something with our API and what can be done with it. Creating a Getting Started guide; generating API Reference Documentation (OpenAPI is Industry Standard); recording Tutorials; collecting Frequently asked questions (FAQ); maintaining a Changelog and defining the terms of service (ToS) are the steps and the chapters of the story our documentation should tell.
HATEOAS is the most advanced form of REST, but it’s not widely used because it requires a big change in thinking for designers and more advanced API clients. The more pragmatic approach is designing the REST APIs to match the Level 2 criteria.
Whichever level you choose, invest time to design the API. Before the construction begins, careful planning through blueprints is essential. Since it is unlikely that you will build only one API, creating and formally defining the design guidelines will be a long-term benefit. Hope this article helps you with that or at least gives you a perspective on the stuff that matters. APIs should be reusable and consistent. Consistency builds trust.