12 API Style How to Design Restful Apis

12 API Style How to Design RESTful APIs #

Hello, I’m Kong Lingfei. Starting today, we will enter the second phase of practical training and begin learning how to design and develop basic functionalities in Go project development. In the next two sessions, we will explore how to design API styles for applications together.

The majority of Go backend services need to write API interfaces to provide external services. Therefore, before development, we need to determine an API style. API style can also be understood as API types. Currently, there are three commonly used API styles in the industry: REST, RPC, and GraphQL. We need to determine which API style to use based on the project requirements and the characteristics of the API style. This decision will have a significant impact on coding implementation, communication methods, and communication efficiency in the future.

In Go project development, REST and RPC are the most commonly used styles. We also used REST and RPC to build example projects in the IAM practical training project. In the next two sessions, I will provide a detailed introduction to REST and RPC. If you are interested in GraphQL, there are plenty of documents and code examples on the GraphQL Chinese official website for you to study on your own.

In this session, let’s first take a look at the design of RESTful API style, and in the next session, we will introduce the RPC API style.

Introduction to RESTful API #

Before answering the question “What is a RESTful API,” let’s first look at what REST means: REST stands for Representational State Transfer, which was proposed by Roy Fielding in his paper “Architectural Styles and the Design of Network-based Software Architectures”. REST itself does not create new technologies, components, or services. It is simply a software architectural style, a set of architectural constraints and principles, rather than a technical framework.

REST has a set of specifications, and any API that meets these specifications can be called a RESTful API. The REST specification treats everything as a resource, meaning that everything on the Internet is a resource. REST architecture includes operations on resources, including retrieval, creation, modification, and deletion, which correspond to the GET, POST, PUT, and DELETE methods provided by the HTTP protocol. The mapping between HTTP verbs and the CRUD operations in the REST style is shown in the table below:

Although the REST style is applicable to many transport protocols, in practical development, because REST naturally complements the HTTP protocol, the HTTP protocol has become the de facto standard for implementing RESTful APIs. Therefore, REST has the following core features:

  • It is centered around resources. Everything is abstracted as resources, and all actions should be CRUD operations on resources.

    • Resources correspond to objects in the object-oriented paradigm, where the object is at the center.
    • Resources are identified using URIs, and each resource instance has a unique URI identifier. For example, if we have a user with the username “admin,” its URI identifier can be “/users/admin”.
  • Resources have states, and the state of a resource is represented using JSON/XML or other formats in the HTTP body.

  • Clients perform operations on server-side resources using the four HTTP verbs, implementing “representational state transfer”.

  • It is stateless, meaning that each RESTful API request contains all the information necessary to complete the operation, and the server does not need to maintain a session. Statelessness is important for server-side scalability.

To avoid confusion, it is important to emphasize the difference between REST and RESTful API: REST is a specification, while a RESTful API is an API interface that meets this specification.

Principles of RESTful API Design #

As mentioned earlier, a RESTful API is an API that follows the REST specification. Therefore, the core of a RESTful API is its adherence to certain principles. So, what are these specific principles?

In the following, I will provide you with a detailed explanation of the design principles of a RESTful API from seven aspects, including URI design and API version management. Then, I will follow it up with an example to help you quickly start a RESTful API service. I hope that after studying this lesson, you will have a clear understanding of how to design a RESTful API.

URI Design #

Resources are identified using URIs, and we should adhere to specific conventions when designing URIs to make our API interfaces more readable and user-friendly. Here are some conventions to follow when designing URIs:

  • Use nouns instead of verbs to name resources, and use plural nouns to represent collections of resources.

    • Collection: A collection of resources. For example, if our system has many users, the collection of these users is known as a collection. The URI identifier for a collection should be domain/resource-name-plural, for example https://iam.api.marmotedu.com/users.
    • Member: A specific individual resource within a collection. For example, a specific user with a specific name in a collection. The URI identifier for a member should be domain/resource-name-plural/resource-name, for example https://iam.api.marmotedu/users/admin.
  • URIs should not end with /.

  • Use hyphens - instead of underscores _ in URIs (although some people recommend using underscores, it is better to adopt a consistent format, and I personally recommend using hyphens).

  • Use lowercase letters in URI paths, and avoid using uppercase letters.

  • Avoid having excessively nested URIs. Having more than two levels of nested resources can be messy, so it is recommended to convert other resources into ? parameters, for example:

    /schools/tsinghua/classes/rooma/students/zhang # Not recommended /students?school=qinghua&class=rooma # Recommended

One thing to note here is that in actual API development, you may find that some operations cannot be easily mapped to a REST resource. In such cases, you can consider the following approaches:

  • Transform an operation into a property of a resource. For example, if you want to temporarily disable a user in the system, you can design the URI as /users/zhangsan?active=false.

  • Treat an operation as a nested resource. For example, the “star” operation in a GitHub repository:

    PUT /gists/:id/star # GitHub star action DELETE /gists/:id/star # GitHub unstar action

  • If none of the above methods can solve the problem, sometimes it is necessary to break these conventions. For example, the login operation does not belong to any resource, and the URI can be designed as /login.

When designing URIs, if you encounter areas of uncertainty, I recommend that you refer to the GitHub Standard RESTful API.

Mapping REST Resource Operations to HTTP Methods #

In general, RESTful APIs use the native HTTP methods GET, PUT, POST, and DELETE to represent CRUD (Create, Read, Update, Delete) operations on resources. The resulting conventions are shown in the table below:

| Operation/Method  | GET     | PUT       | POST       | DELETE    |
|-------------------|---------|-----------|------------|-----------|
| Retrieve Resource | Yes     | No        | No         | No        |
| Update Resource   | No      | Yes       | No         | No        |
| Create Resource   | No      | No        | Yes        | No        |
| Delete Resource   | No      | No        | No         | Yes       |

Operations on resources should adhere to the principles of safety and idempotence:

  • Safety: It does not change the state of the resource and can be understood as read-only.
  • Idempotence: The effect of performing the operation once vs performing it multiple times should be equivalent in terms of resource state change.

Below is a comparison of the safety and idempotence of resource operations when using different HTTP methods:

| Operation/Method | Safe | Idempotent |
|------------------|------|------------|
| GET              | Yes  | Yes        |
| PUT              | No   | Yes        |
| POST             | No   | No         |
| DELETE           | No   | Yes        |

When using HTTP methods, there are two points to note:

  • GET results should be able to be used for PUT and POST operations as much as possible. For example, if you obtain information about a user using the GET method, the caller can modify the user’s email and then use the PUT method to update it. This requires that the resource attributes of the GET, PUT, and POST operations are consistent.

  • If there is a need to change the state/attributes of a resource, use the PUT method. The POST method should only be used for creating resources or batch deletions. When designing APIs, there is often a need for batch deletion, where multiple resource names need to be included in the request for deletion. However, the DELETE method in HTTP cannot carry multiple resource names. In such cases, you can solve this problem in the following three ways:

    • Send multiple DELETE requests.
    • Include multiple IDs in the operation path, with a separator in between, for example: DELETE /users?ids=1,2,3.
    • Use the POST method directly for batch deletion and pass the list of resources to be deleted in the request body.

Among these, the second approach is the one I recommend the most, as it uses the appropriate DELETE verb and does not require multiple DELETE requests.

It is important to note that each of these three methods has its own use cases, and you can choose accordingly based on your needs. If you choose one method, the entire project should follow the same approach.

Unified Response Format #

Typically, a system’s RESTful API exposes interfaces for multiple resources, and the response format of each interface should be consistent. Additionally, each interface returns both success and failure messages, and the format of these two types of messages should also be consistent. Otherwise, the client code will have to adapt to different response formats for each interface, which will greatly increase the learning and usage costs for users.

There is no strict standard for the format of response, so you can return different formats based on the actual business needs. In this column, in Lecture 19, I will recommend a response format that is widely used and recommended in the industry.

API Versioning #

Over time, with changing requirements, an API often cannot meet the existing needs. In such cases, modifications need to be made to the API. When making changes to an API, it should not affect the normal usage of other calling systems, which requires the API changes to be backward compatible, i.e., new and old versions coexist.

However, in practical scenarios, it is possible that the same API cannot be backward compatible. In such cases, the best solution is to introduce API versioning from the beginning. When backward compatibility is not possible, a new version is introduced while the old version remains unchanged. This ensures the availability and security of the service, as well as meets new requirements.

API versions can be indicated in different ways. In RESTful API development, the version can usually be placed in one of the following three locations:

  • In the URL, for example: /v1/users.
  • In the HTTP header, for example: Accept: vnd.example-com.foo+json; version=1.0.
  • In the form parameter, for example: /users?version=v1.

In this course, the version identifier is placed in the URL, for example: /v1/users. The benefit of this approach is that it is straightforward, and many excellent APIs such as GitHub, Kubernetes, and Etcd use this method.

It is worth noting that some developers do not recommend putting the version in the URL, as they believe that different versions of the same API can be understood as different representations of the same resource, so the same URI should be used. There is no strict standard for this, so you can choose one approach based on the actual needs of the project.

API Naming #

There are generally three naming conventions for APIs: camel case (e.g. serverAddress), snake case (e.g. server_address), and kebab case (e.g. server-address).

Both camel case and snake case require switching input methods, which increases complexity and is prone to mistakes. Therefore, I recommend using kebab case. GitHub API, for example, uses kebab case, such as selected-actions.

Unified Pagination/Filtering/Sorting/Search Functionality #

The query interface for REST resources usually needs to implement pagination, filtering, sorting, and searching functionalities, as these functionalities can be used for any REST resource. Therefore, they can be implemented as a common API component. Let’s introduce each of these functionalities below.

  • Pagination: When listing all members under a collection, pagination should be provided, for example: /users?offset=0&limit=20 (limit specifies the number of records to return, offset specifies the starting position of the records to return). The introduction of pagination can reduce the response delay of the API and prevent returning too many entries, which would cause the server/client response to be very slow, or even cause the server/client to crash.
  • Filtering: If a user does not need all the status attributes of a resource, they can specify which attributes to return in the URI parameters, for example: /users?fields=email,username,address.
  • Sorting: Users often want to list the top 100 members in a collection based on creation time or other factors. In this case, sorting parameters can be specified in the URI parameters, for example: /users?sort=age,desc.
  • Searching: When a resource has too many members, users may want to quickly find the member they need through searching, or search for resources of a certain type with a specific name. In such cases, search functionality needs to be provided. It is recommended to use fuzzy matching for searching.

Domain Name #

API domain name settings can be done in two main ways:

  • https://marmotedu.com/api: This approach is suitable when the API will not be further expanded in the future. For example, initially only one set of API systems is under the marmotedu.com domain, and there will only be one set of API systems in the future as well.
  • https://iam.api.marmotedu.com: If another API system will be added under the marmotedu.com domain in the future, it is best to have dedicated API domains for each system. For example: storage.api.marmotedu.com, network.api.marmotedu.com. This is the approach Tencent Cloud uses for its domains.

With this, we have covered the core principles of REST design. It is worth noting that different companies, teams, and projects may adopt different REST design principles. The principles listed above are generally accepted by the industry.

There are still some principles in REST design that have more content and can be covered as separate modules, so they will be discussed later. These include RESTful API security, status codes, and authentication.

REST Example #

In the previous section, we discussed some concepts and principles. Here, we will teach you how to quickly start a RESTful API service using Go with a “Hello World” program. The example code is stored in gopractise-demo/apistyle/ping/main.go.

package main

import (
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/ping", pong)
	log.Println("Starting http server ...")
	log.Fatal(http.ListenAndServe(":50052", nil))
}

func pong(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("pong"))
}

In the above code, we registered a “pong” handler to the HTTP server using http.HandleFunc. In the pong handler, we wrote the actual business logic to return the string “pong”.

After creating the main.go file, execute go run main.go in the current directory to start the HTTP server. In a new Linux terminal, use the curl command to test the HTTP request:

$ curl http://127.0.0.1:50052/ping
pong

Summary #

In this lesson, I introduced one of the two commonly used API styles - RESTful API. REST is an API specification, and RESTful API is an API interface that conforms to this specification. The core of RESTful API is its adherence to the specification.

In the REST specification, resources are identified using a URI. Resource names are expressed as nouns instead of verbs, and they are usually in plural form. Resources are divided into two types: Collection and Member. In RESTful API, POST, DELETE, PUT, and GET are used to represent the operations of creating, deleting, updating, and retrieving REST resources. Different combinations of HTTP methods, Collection, and Member will result in different operations. You can refer to the table in the section Mapping REST Resource Operations to HTTP Methods for the specific mappings.

To facilitate user usage and understanding, the return format of each RESTful API and the formats of error and success messages should be consistent. RESTful API needs to support API versioning, and the versions should be backward compatible. We can include the version number in the URL, HTTP header, or form parameters, but I recommend putting it in the URL, for example /v1/users, as this form is more intuitive.

In addition, we can use the spine naming convention to name API interfaces. For a REST resource, its query interface should also support pagination, filtering, sorting, and searching. These functionalities can be implemented using the same mechanism. The domain name of the API can be in either of the formats: https://marmotedu.com/api or https://iam.api.marmotedu.com.

Finally, in Go, we can use the net/http package to quickly start a RESTful API service.

Homework #

  1. Use the net/http package to quickly implement a RESTful API service and implement the /hello endpoint, which will return the string “Hello World”.
  2. Think about whether the RESTful API style meets your current project needs. If not, what is the reason?

Looking forward to seeing your thoughts and answers in the comments section. Feel free to discuss any questions related to RESTful API. See you in the next class!