The main purpose of writing this article is to summarize my years of experience in RESTful API design and share it with you.

In order to better explain some design problems and points to pay attention to, this article will focus on the same example from the beginning to the end. Of course, this example is a YY REST architecture software system. Let’s call it GoodHR, and the URL for the system is www.goodhr.com (coincidence, if any).

Simply speaking RESTful API is actually REST system architecture design concept based on the WEB environment HTTP protocol to describe the system interface. Therefore, the entire RESTful API design is divided into two major sections, describing the URI of the resource and the method for performing row operations on the resource.

The article will be divided into three main parts: 1) Design practice of Uniform Resource Identifier (URI) for resources; 2) For method design practices, HERE, I only describe the most common methods (POST, PUT, GET, and DELETE); 3) For some special scenarios, such as the design of RESTful apis to support complex query/search capabilities, support asynchronous processing RESTful apis, etc., a case by case explanation.

URI design

In RESTful API design, the main purpose is to make the interface more self-descriptive, so that the interface can be better in terms of ease of use and maintainability.

The URI of a resource is simply a description of how to locate a resource in a Web environment. A RESTful API’s resource URI usually consists of two parts: Path and Query Parameters. Let me introduce and explain them respectively.

Path

Path is used to describe the access Path of a resource.

In RESTful API design, Path content also contains Path parameters, which are dynamic parameters. For example, if we have a Path pointing to a specific company resource object, we will have the following Path design

http://www.goodhr.com/api/v1/companies/ id} {companyCopy the code

Here {company ID} is the Path parameter, which can be replaced with a specific ID value to indicate which company resource object. The Path Parameter is a mandatory Parameter in the URI design concept of the RESTful API. In other words, the value of the Path Parameter must be given, otherwise the RESTful API will not be used, or another API will be accessed.

In fact, in the face of different scenarios, there are different practices in Path design. I will list the common ones below:

1. Set of resource objects

If used to describe a resource (an aggregation of resources), use the plural form, as in the following example:

http://www.goodhr.com/api/v1/companies/66/employees
Copy the code

2. Separate resource objects

If it is used to describe a resource, then the resource must have a unique identifier to identify the resource, as in the following example:

http://www.goodhr.com/api/v1/companies/66/employees/} {employee idCopy the code

3. Resource dependency

Subordinate resource relationships, for example, where an employee is subordinate to a company, should be expressed as follows:

http://www.goodhr.com/api/v1/companies/66/employees/1001
Copy the code

Another function of this expression is that it represents a strong dependency on the life cycle of its resources. To put it simply, if a company is deleted, its employees should not be able to access or modify it any more.

4. Resource indexes

Indexable resources. For example, an employee can join multiple departments in a company. The relationship between a department and an employee is not a strong dependency relationship.

http://www.goodhr.com/api/v1/companies/66/departments/ id} {department/employeesCopy the code

This representation is not that different from the subordinate resource URI, except that departments are represented by adding a single-level path that represents a group of employees.

Query Parameter

The Query parameter is the question mark (?) in the URI. The subsequent key-value argument is an optional argument in the design of the RSETful API — there will be no access to other resources, and it will not cause the API to fail. For example, if we need to retrieve all employee resource objects under a company, our API could be designed as follows:

http://www.goodhr.com/api/v1/companies/66/employees?pageStart=1&pageSize=100
Copy the code

PageStart and pageSize are query parameters that can be used as page-turning parameters. If pageStart or pageSize is missing, There will also be a default logical implementation, where we will generally say that if pageStart does not give, we will start at 1 by default, and if pageSize does not give, we will use the default pageSize of 20 instead.

Methods to design

The most common methods of manipulating resources in RESTful apis are POST, PUT, GET, and DELETE. In the following sections, I’ll explain how to design each method in detail.

In the official HTTP protocol specification, the GET, PUT, and DELETE methods are idempotent, while POST is not.

1. POST method

The POST method creates a new resource for the resource described by the URI. A POST method can carry a request body. RESTful apis generally use a JSON body. The return type of the POST method is also JSON, and the HTTP status code should be 201 (Created).

There are two scenarios for defining a POST RESTful API:

1. The resource Id is uncertain

If the Id of the resource you want to create is distributed by the Server, then the URI should generally follow the design style of the resource set. For example, create a new employee with the following URI

http://www.goodhr.com/api/v1/companies/66/employees
Copy the code

Request body

{
    "firstname": "Steve"."lastname": "Bill"."birthDate": "1982-01-01"."gender": "male"."hiredDate": "2019-01-01"."socialSecurityNumber": "1234567890123456789"
}
Copy the code


Returns the result

{
    "companyId": 66,
    "id": 1001,
    "firstname": "Steve"."lastname": "Bill"."birthDate": "1982-01-01"."gender": "male"."hiredDate": "2019-01-01"."socialSecurityNumberMask": "123 * * * * * * * * * * * * 789"."creationTime": "The 2019-01-01 09:00:01"."updatedTime": "The 2019-01-01 09:00:01"
}
Copy the code

Here the value of the “ID” field is distributed by the server when an Employee record is created.


2. The resource Id can be determined

In one case, the Id of the resource you want to create is predetermined and does not need to be distributed by the Server at creation time. In this case, the URI should follow the design style of the individual resource.

For example, create a new department with the following URI

http://www.goodhr.com/api/v1/companies/66/departments/dept-acct
Copy the code

Request body

{
    "name": "Accounting"."introduction": "The finance department of the company."
}
Copy the code


Returns the result

{
    "companyId": 66,
    "code": "dept-acct"."name": "Accounting"."introduction": "The finance department of the company."
    "creationTime": "The 2019-01-01 09:00:01"
}
Copy the code


PUT method

PUT is generally used to update a resource. According to the HTTP specification, PUT is an idempotent operation. Therefore, the interface design of PUT must be full update of resource information, not partial update. Here we use employees as an example:

The URI should be designed in the style of the individual resource, and the returned HTTP status code should be 200 in the case of success

1. Resource object properties are relatively simple

In general, some resource objects contain relatively simple attribute information, so in this case, the standard PUT RESTful API can be designed, such as employee information update:

http://www.goodhr.com/api/v1/companies/66/employees/1001
Copy the code

The design of the request body here is to update the entire employee information (below) rather than partially update the request body

{
    "firstname": "Kevin"."lastname": "Bill"."birthDate": "1982-01-01"."gender": "male"."hiredDate": "2018-03-01"."socialSecurityNumber": "1234567890123456789"
}
Copy the code


Returns the result

{
    "companyId": 66,
    "id": 1001,
    "firstname": "Kevin"."lastname": "Bill"."birthDate": "1982-01-01"."gender": "male"."hiredDate": "2018-03-01"."socialSecurityNumberMask": "123 * * * * * * * * * * * * 789"."creationTime": "The 2019-01-01 09:00:01"."updatedTime": "The 2019-01-05 14:56:12"
}
Copy the code


2. Resource objects have many properties

Sometimes, we have a situation where a resource has a lot of attributes, and those attributes are classified by some type, for example, the company resource object has basic information, tax information, board of directors information, business status, and so on, and in this case, If we use a RESTful API to do the full update, in fact, both from the EASE of use of this API, and later if the access to resources management have certain problems. In this case, I would design the API to do separate URIs for different types of attributes as follows:

Company Basic Information

http://www.goodhr.com/api/v1/companies/66/profile
Copy the code

Company tax information

http://www.goodhr.com/api/v1/companies/66/tax-info
Copy the code

Information of the Board of Directors

http://www.goodhr.com/api/v1/companies/66/executive-info
Copy the code


Company status

http://www.goodhr.com/api/v1/companies/66/status
Copy the code


The DELETE method

This method is idempotent, so the interface’s implementation rule is that the first deletion of an existing resource and the subsequent deletion of a nonexistent resource should return the same result — HTTP status code = 204.

1. Delete an individual resource

Uris should be designed in the style of individual resources, without giving the request message body for example, we delete an employee.

http://www.goodhr.com/api/v1/companies/66/employees/1001
Copy the code

There are two ways to return a message: 1. Return an empty message with HTTP status code 204. 2. A result can be returned containing the key ids of the deleted employee, for example, the company Id and 1001 in the URI above.

{
    "companyId": 66,
    "employeeId": 1001}Copy the code

2. Delete a batch of resources

This does not happen very often, but it is possible, for example, to delete the records of all employees who have been absent for more than 3 years. This URI can be designed in the following style:

http://www.goodhr.com/api/v1/companies/66/employees/_resigned-exceed-years/3
Copy the code

“_kiln-beyond-years” is here a brush-condition for the Employees resource set, and the following 3 is the value of the brush-condition.

Here I suggest giving a return value, which is mainly used to report a result of the delete, as follows:

{
    "companyId": 66,
    "numberOfDeletedEmployees": 132}Copy the code

GET

The usage scenario of this method is easy to understand, which is to obtain resources. However, in the actual practice process, I found that the scenario of GET method is the most complex. For example, complex multi-criteria searches.

1. Obtain an individual resource object

Gets a specified resource object, for example, an employee given an employee ID

http://www.goodhr.com/api/v1/companies/66/employees/1001
Copy the code

The return value

{
    "companyId": 66,
    "id": 1001,
    "firstname": "Steve"."lastname": "Bill"."birthDate": "1982-01-01"."gender": "male"."hiredDate": "2019-01-01"."socialSecurityNumberMask": "123 * * * * * * * * * * * * 789"."creationTime": "The 2019-01-01 09:00:01"."updatedTime": "The 2019-01-01 09:00:01"
}
Copy the code


2. Obtain the resource object set

For example, get all the employees under a company

http://www.goodhr.com/api/v1/companies/66/employees?pageStart=1&pageSize=100&orderBy=creationTime&order=DESC
Copy the code

As for the design of the returned data of a resource set, it is necessary to add a layer of fields to better represent a set of data and show what conditions are used to obtain this set of data. The design can be considered as follows:

{    
    "queryConditions": {},
    "pagination": {
        "pageStart": 1,
        "pageSize": 100,
        "sorting": {
            "orderBy": "creationTime"."order": "DESC"}},"data": {
        "size": 100,
        "records": [{"companyId": 66,
                "id": 1001,
                "firstname": "Steve"."lastname": "Bill"."birthDate": "1982-01-01"."gender": "male"."hiredDate": "2019-01-01"."socialSecurityNumberMask": "123 * * * * * * * * * * * * 789"."creationTime": "The 2019-01-01 09:00:01"."updatedTime": "The 2019-01-01 09:00:01"}... ] }}Copy the code

When obtaining a group of resource objects, try to include the parameter information used during obtaining in the returned object. For example, above, queryConditions are included to represent the search conditions when obtaining the resource. There is no one here, so it is empty. Pagination is used to specify paging information for resources in data, and the data field contains not only the records set returned but also the actual records size to increase the ease of use of this interface.

3. Obtain the resource set object by searching

Providing the ability to search for resources is a common capability in many systems, such as obtaining an employee’s name by searching by name, or a combination of criteria, such as name plus age, etc. The biggest problem with this RESTful API design is that GET itself cannot provide the request body, so it is generally supported as a Query Parameter.

For example, we want to search firstName for Steve’s employees younger than 60 years old and return them in reverse order:

http://www.goodhr.com/api/v1/companies/66/employees?firstname=Steve&age=%3C60
pageStart=1&pageSize=100&orderBy=hiredDate&order=DESC

{    
    "queryConditions": {
        "firstname":"steve"."age": "< 60"
    },
    "pagination": {
        "pageStart": 1,
        "pageSize": 100,
        "sorting": {
            "orderBy": "creationTime"."order": "DESC"}},"data": {
        "size": 13."records": [{"companyId": 66,
                "id": 1001,
                "firstname": "Steve"."lastname": "Bill"."birthDate": "1982-01-01"."gender": "male"."hiredDate": "2019-01-01"."socialSecurityNumberMask": "123 * * * * * * * * * * * * 789"."creationTime": "The 2019-01-01 09:00:01"."updatedTime": "The 2019-01-01 09:00:01"}... ] }}Copy the code

In fact, many people are afraid to use GET to do complex search, because the URL length limit will be a problem, but according to the HTTP standard, 2048 characters is no problem, so the general system provides the search capability for resources should be enough to meet. Of course, we can’t deny the fact that there are some complex search scenarios, if so, look at the following three parts of some design practices for specific scenarios.

Special scenario

Based on the 2/8 theory, the design practice in the above two parts OF URI and method covers 80% of the scenarios, while in this part, I focus more on the 20% complex scenarios. Of course, what I summarize is only the situations I have encountered in designing RESTful apis for systems in different industries in the past 10 years. Not all scenarios are covered, so if you find that the scenario I describe does not cover yours, you can choose to send me a message and we can discuss how to design it together :).

1. Super complex search

In rare scenarios, it is relatively common to provide such overly complex search RESTful apis. In this case, if the standard GET+ Query paramters are used, there is a possibility that the url length will exceed the standard requirements. Therefore, we can only handle special cases. In general, we will use POST method instead. The search criteria are then provided as JSON in the request body.

For example, we need to search for an employee’s leave history:

http://www.goodhr.com/api/v1/companies/66/timeoff-records?pageStart=1&pageSize=100&orderBy=creationTime&order=DESC
Copy the code

Use the POST method to send search criteria information:

{    
    "type": [
        "vocation"."sick"]."appliedDateRange": {
        "startDate": "2019-01-01"."endDate": null
    },
    "departments": ["dept-acct"."dept-tech"."dept-marketing"]}Copy the code

Returns the object

{    
    "queryConditions": {    
        "type": [
            "vocation"."sick"]."appliedDateRange": {
            "startDate": "2019-06-01"."endDate": null
        },
        "departments": ["dept-acct"."dept-tech"."dept-marketing"]},"pagination": {
        "pageStart": 1,
        "pageSize": 100,
        "sorting": {
            "orderBy": "creationTime"."order": "DESC"}},"data": {
        "size": 13."records": [{"companyId": 66,
                "id": 10293,
                "applicantId": 1002,
                "applicationDateTime": "The 2019-01-01 09:00:01"."approverId": 98,
                "type": "vocation"."timeoffBegin": "2019-06-01 AM"."timeoffEnd": "2019-06-01 PM"."creationTime": "The 2019-01-01 09:00:01"."updatedTime": "The 2019-01-01 09:00:01"}... ] }}Copy the code

2. Asynchronous processing

If it is need to provide, for example, the backup data and generate reports as a result, the complex computing task, or the need to deal with the artificial, tend to use asynchronous processing way, in the design of RESTful API, the so-called asynchronous and asynchronous API in the system is different is that he is not a asynchronous scheme based on threads, and more is, Request processing is initiated through one RESTful API, and the result of that request processing is retrieved through another API. There are two ways to design this asynchronous processing, which I describe below.

A. Separation of execution and acquisition

To put it simply, you need to trigger API A to create A processing request task, and then rotate through API B to get the result. For example, we want to generate a please add statistics report for all employees:

The first RESTful API, which we use to create generated reports, uses the POST method because each time this interface is called, a new report generation task is created

http://www.goodhr.com/api/v1/companies/66/statistics/timeoff
Copy the code


Request body

{    
    "contentType": "excel"."dateRange": {
        "startDate": "2018-01-01"."endDate": "2018-12-31",}}Copy the code


Returns the object

{  
    "jobId": "timeoff-100202"."creationTime": "The 2019-01-01 09:00:01"."contentType": "excel"."dateRange": {
        "startDate": "2018-01-01"."endDate": "2018-12-31",}}Copy the code


Here, jobId is used to get the result of this asynchronous processing task from the second API.

The second RESTful API is used to GET the statistics report, where we use the GET method.

http://www.goodhr.com/api/v1/companies/66/statistics/timeoff/{jobId}
Copy the code

Returns the object

{  
    "jobId": "timeoff-100202"."creationTime": "The 2019-01-01 09:00:01"."status": "finished"."retrivalLocation": "https://static.goodhr.com/reports/timeoff-100202.xls"
}
Copy the code

Of course, it is also possible that the report is still being generated, in which case the status in the returned object might be “Processing” and the retrievalLocaiton null.


B. Active push is complete

In contrast to A, this design scheme is to obtain results in the way of pull, while this scheme is to push. If the RESTful API that generates timeoff reports is designed in this way, there will be some complexity in the overall design. Of course, the advantage is that the system will be much less pressured by the push design.

In this design scheme, we only need to provide the RESTful API of the first POST in A to do the same task generation of report generation. The request body does not change, and a field “callbackUrl” needs to be added to the returned object, as follows:

{  
    "jobId": "timeoff-100202"."creationTime": "The 2019-01-01 09:00:01"."callbackUrl": "https://another-system.somewhere.com/receive-reports/timeoff"."contentType": "excel"."dateRange": {
        "startDate": "2018-01-01"."endDate": "2018-12-31",}}Copy the code

The callbackUrl here is actually a Webhook design. For Webhook, if it is an internal system, it is suggested to simply add a host whitelist check, and if it is a SaaS Open API to provide extension capabilities to the third party, Then it is necessary to build an additional webhook management function to ensure sufficient security, such as cross-domain attack or request interception.

3. Super multi-field resource objects

Sometimes, some resource object contains many attributes, dozens or even hundreds of attributes, and in this case, we get the resource through a RESTful API. If we just get a certain resource object by Id, that’s actually acceptable. But if we get a set of resource objects, So no matter system overhead or network overhead, will produce unnecessary waste.

The design of RESTful apis for this resource can be considered to provide a Query parameter in the design of its URI to tell the server which attributes of the resource need to be returned, for example, for access to corporate resources.

{http://www.goodhr.com/api/v1/companies/66?fields=} attribute typesCopy the code

Here, the fields value can be provided according to the attributes of a company object, for example, adding tax-info to return tax information and adding executive-info to return management information. Of course, if Fields does not, we should have a default return content logic, for example, return company information as the default return property.

This kind of design scheme in the fields of best is a kind of property, rather than an attribute, so on the readability of the code, and server-side code implementation complexity and flexibility, would have a relatively good balance, assumption, if you need the interface of the caller to get specific each attribute names to give, so, this would be a disaster.

4. Idempotent operation design

The RESTful API design is very simple. Generally, it provides a query parameter on the URI to represent a transaction ID, allowing you to safely retry each transaction.

Assuming that our system’s logic for creating an employee record involves interacting with other systems and updating data, we need to modify the previous example of creating an employee record in the POST method to support idempotent operations.

http://www.goodhr.com/api/v1/companies/66/employees
Copy the code

Request body

{
    "firstname": "Steve"."lastname": "Bill"."birthDate": "1982-01-01"."gender": "male"."hiredDate": "2019-01-01"."socialSecurityNumber": "1234567890123456789"
}
Copy the code


Here, we add a field, transactionId, to the returned result to represent the transaction requested for this creation.

Returns the result

{
    "transactionId": "e721ac103ckc910ck20c". }Copy the code


If you fail to create employee records due to an exception (other than 204 Status code), then you can add the transactionId in the second retry to ensure that the server can implement an idempotency operation as follows:

http://www.goodhr.com/api/v1/companies/66/employees?transactionId=e721ac103ckc910ck20c
Copy the code

The last

There are a few basic design practices that I find useful not just for RESTful apis, but for any system API.

The Tell, Don’t ask principle

The main emphasis of this principle is that each interface should be able to complete the entire business logic, rather than the caller combining multiple interfaces to complete the business logic.

For example, when creating an employee record, it is necessary to call a system interface of the government to verify its SIN (social security number), so the best implementation is to have an employee record creation interface to complete all the SIN verification and record creation at one time, as follows:

good

POST: http://www.goodhr.com/api/v1/companies/66/employees
Copy the code


Request body

{
    "firstname": "Steve"."lastname": "Bill"."birthDate": "1982-01-01"."gender": "male"."hiredDate": "2019-01-01"."socialSecurityNumber": "1234567890123456789"
}
Copy the code


Instead, sometimes someone will split the two logic into two separate interfaces to provide the following effect

bad

Verify your Social Security number

GET: http://www.goodhr.com/api/v1/sin-record/1234567890123456789
Copy the code


Create employee records

POST: http://www.goodhr.com/api/v1/companies/66/employees
Copy the code


Request body

{
    "firstname": "Steve"."lastname": "Bill"."birthDate": "1982-01-01"."gender": "male"."hiredDate": "2019-01-01"."socialSecurityNumber": "1234567890123456789"
    "sinVerfiied": true
}
Copy the code

version

Make sure you have versioning, not only for easy maintenance, but also for easy upgrades and refactorings.

In RESTful apis, versioning is usually done by adding version information to the Path uri

http://www.goodhr.com/api/v {version}Copy the code

The version number section can be numeric or whatever you want, for example, some may use a date such as 20190101.


This article is forwarded to my own wechat public number “jingji body”, if you think my article is still useful, welcome to pay attention to 🙂