Have a good ReSTful API
In this article, I would like to talk about several aspects of ReSTful API.
- Define resources, design URL address. When we design the endpoint address for the ReSTful interface, the way how we access the resources dictates the design of the endpoint URLs.
- ReST on HTTP. Building the ReSTful API on HTTP becomes the de facto standard nowadays. An HTTP based API requires proper usage of HTTP methods, HTTP mime types, and HTTP error codes.
- Let the ReSTful API talk. Make the ReSTful API self-explainable and navigatable.
- Implementation of ReSTful API. YAML, entities/DTO/VO, and more.
Define resources, design URL address
In the early days, there are all kinds of concepts and implementations on ReST. One genre of ReSTful API is web services/RPC-like API. The designer uses verbs when defining the endpoint addresses. For example, suppose we want to design an API to access employees, the following endpoint address could be used to create an employee:
Note that it is a good practice to use a hyphen to separate the words here. This is both human-friendly and machine-friendly since it is easy to read and easy to parse.
While an employee is created, we need other operations to manage it. Basically, CRUD (create/read/update/delete) operations are needed too. We would need some endpoint URLs like:
If you use ReSTful definition to check against this API, nothing seems wrong on such kind of API. It is client-server architecture and serves the purpose of changing the state in the backend server. Do you think it is a little bit verbose? Of course, since it uses verbs, how can’t it be verbose? Strictly speaking, these are RPC calls. It is a function-oriented API. It is not very good since it doesn’t use HTTP efficiently.
ReSTful API is a resource-oriented API. When we design the API, first we need to understand:
- What kind of resource we are exposing to the client?
- How we are going to organize the resources?
- How are we going to versioning the API
The first question is fundamental, which decides what this API is essentially designed for. Usually, a resource is associated with a backend entity, which could be backed up by a physical database table or could just be a virtual entity.
Similarly to the RPC-like API, we would need some methods to operate on the entity. For example, suppose we have an employee entity, we want to be able to create an employee, update an employee, list all employees, list employees by certain criteria, get the details of a customer, and delete an employee.
Different from RPC-like function-oriented API, a resource-oriented API doesn’t use verbs in the URL address. The URL reflects the resource to access. so the endpoint URL will be something look like this:
To drill down to the details of an employee with ID = 123, we have:
Note the ID is a path parameter. These two endpoint URLs are probably all we need to operate on the employee resource. The operations on the resource rely on the HTTP protocol, so I decide it should be the content of the next section. Otherwise, there won’t be much to write in the second section, right?
Very often, we have a hierarchy of entities, such as a department that might have sub-department, which might have employees. Each entity is a type of resource.
How we are going to organize the resources? I think it is a good practice to categorize the APIs if the resources can be grouped by their nature.
For example, if the employee and department are grouped as org API, then the API URL could be something like:
There are many versioning strategies.
- Versioning using the URL. Currently, versioning with URL is widely adopted. I have seen two approaches.
One approach is to put version after the first category level: http://hostname:port/category/version/entity
Another approach is to put version before the first category level: http://hostname:port/version/category/entity
- Versioning using the HTTP header by adding a new HTTP header to indicate the API version
- Versioning using the entity field, be it JSON or other formats.
Since the API is versioned, there must be a deprecation policy to deprecate the previous API. To deprecate the API depends on the company's customer policy. When the API client calling the deprecated API, there should be a clear error message to tell the client that the current version is deprecated and which version should be used.
ReST on HTTP
As aforementioned, the operations on the resource rely on the HTTP protocol. In HTTP, we have the following methods at our disposal:
GET method: Idempotent operation, meaning won’t change the state of the backend resources no matter how many times this method is executed. This method is used to retrieve information.
It can be used to retrieve a set of entities. In this case, I would suggest returning a set of a short version of entities. Details in the implementation section.
- To get all entities:
200 OKresponse code, be it returns a list of entities or an empty array as the payload.
- To get the entities with query criteria:
200 OKresponse code, be it returns a list of entities or an empty array.
- To get the details of a single entity by ID:
POST method: Non-idempotent operation. Imagine you are using a forum and writing a comment about someone’s post. You type, “it is really funny”. Then, you click the Post button, after 4 seconds, nothing seems changed. You are confused, “Maybe I didn’t click the button?” You click the Post button again. 5 seconds later, two posts show, “it is really funny” after another “it is really funny”. There you go, the POST method is not idempotent.
The POST method is used to create an entity. Non-idempotent means a second POST operation might create another entity (My guess is that you usually don’t want this behavior).
To create an entity:
- Successful POST returns
201 Createdresponse code with the created entity as the payload (The ID of the created entity should be in the payload).
- Failure POST due to the entity with the same ID already exists returns
418 I'm a teapotresponse code (I am just kidding). Please return
409 Conflictresponse code instead (no kidding).
- Failure POST due to the incorrect JSON format in the request returns
400 Bad Requestresponse code.
- Failure POST due to server resources are not available, for example, database connection down, returns
500 Internal Server Errorresponse code.
PUT method: Idempotent method. This method is used to create or update an entity. In case of an update, to be accurate, it replaces an existing entity. The payload of the PUT method must have all the fields. Since it is a replace operation, a second PUT won’t change the update result, therefore it is idempotent.
To update an entity by its ID:
Note the ID should be the path parameter.
- Successful PUT returns
200 OKresponse code with the created or updated entity as the payload.
- Failure due to the incorrect JSON format in the request returns
400 Bad Requestresponse code.
- Failure due to server-side errors, for example, database connection down, returns
500 Internal Server Errorresponse code.
Depending on your scenario, you might create a resource if the specified ID can’t be found. In this case, the database generated ID should be independent of the entity ID. For example, if you want to insert an employee record with employ ID 123 as a row into the database table. In this case, suppose you have an ID column which is the database generated ID, you should have another column for the employee_ID.
DELETE: Non-idempotent operation. This method is used to delete the resources created by using the POST method.
To delete an entity by its ID:
PATCH: Non-idempotent operation. This method is used to partially update an existing resource. Unlike the PUT method always updating the resource with complete payload, this one updates the selected content only.
Other useless ones, which are less used ones, won’t introduce here.
Let the ReSTful API talk
When designing the API signature, using HTTP words and clarifying the resource basically reviews the operation and the entity your are operating on.
A good practice to design the entity is to include the following 3 basic fields:
- ID: id of the entity
- Name: name of the entity
- Description: description of the entity
This way, when the entity is displayed, regardless of the UI element, you can always give clear information about the entity.
HATEOAS is a very convenient feature for a navigatable ReSTful API. The benefit of the HATEOAS is that it allows you to navigate to the desired page by using the link provided in the ReST response. If you have a system that accesses the API programmatically, this feature spares you from hard-coding the ReST URL in the code. In addition, a well designed HATEOAS provides a complete API site-navigation-map, which gives you a clear picture of what APIs are provided and how you can use the APIs. Spring Boot has a nice feature to allow you to add the desired HATEOAS link.
How to design the HATEOAS link depends on your use cases. Here is the basic thumb of rules:
- The response of getting all entities API provides the links for API to access a single entity.
- The response of a single entity API provides the link to return back to get all entities API.
Implementation of ReSTful API
Note: This section is Java & Spring Boot based.
Swagger has a tool for the ReSTful API code generation. The name of the tool is swagger-codegen, make sense?
swagger-codegen can take YAML configuration file as the input to generate Java code. For the YAML ReST configuration, please refer to this page.
When using the swagger-codegen, you need to specify some parameters. Depends on the version of swagger-codegen, the parameter names could be different, but it would be obvious enough to map the new version parameter names to the old version ones. Basically, the following parameters are needed:
- API package: package for generated API classes, which are the ReST API endpoint-entry-classes. Meaning, the ReST requests will be handled by the classes from this package. In the generated package, the API classes shouldn’t be touched, while the controller classes should be filled with your own code.
- Model package: package for generated models, which are the model objects/entities defined in definition section of YAML file. The generated model is actually the data model for the ReSTful interface. It reflects the internal data model, but not necessarily the same as the internal data model, so it also provides a layer of abstraction to protect the internal data model.
- Invoker package: root package for generated code, which also serves as the basePackages for the Spring ComponentScan annotation. All classes under this package with Spring annotation such as Component or Service etc. will be picked up by the Spring and be available in the Spring application context.
After the code is generated, Swagger2SpringBoot.java is the entry class that starts the Spring Boot application. Use the generated API interfaces and model classes as the ReST interfacing layer. Then you can add your implementation of the API interfaces. There are many articles about this. My article ends up here. Hope it helps.