21 Network Programming How to Play With Restful API Services in Go

21 Network Programming - How to Play with RESTful API Services in Go #

Starting from this lesson, I will take you through the fifth module of this column. In this module, you will learn the most commonly used coding operations in our projects, which are writing RESTful APIs and RPC services. In actual development projects, the services you write can be used by other services, forming a microservices architecture. They can also be called by the front-end, achieving front-end and back-end separation.

Today, I will first introduce what RESTful API is and how Go language can play with RESTful API.

What is RESTful API #

RESTful API is a set of specifications that regulates how we operate on resources on a server. Before understanding RESTful API, let me introduce to you the HTTP Methods, because RESTful API is closely related to them.

Speaking of HTTP Methods, the most common ones are POST and GET. In fact, in the HTTP 0.9 version, there was only one GET method, which is an idempotent method used to retrieve resources on the server, i.e., the method we use when we directly enter a URL in the browser and press Enter.

In the HTTP 1.0 version, the HEAD and POST methods were added. Among them, the most commonly used one is the POST method, which is generally used to submit a resource to the server, leading to changes in the server’s resources.

As the network became more and more complex, it was found that these two methods were not enough, so more methods were added. Therefore, in the HTTP 1.1 version, they were increased to 9 methods, including HEAD, OPTIONS, PUT, DELETE, TRACE, PATCH, and CONNECT. Let me introduce the functions of each of them to you.

  1. The GET method requests the representation of a specified resource. The use of GET requests should be limited to retrieving data.
  2. The HEAD method is used to request a response that is identical to a GET request, but without a response body.
  3. The POST method is used to submit an entity to the specified resource, often causing a change in the server’s state or a side effect.
  4. The PUT method is used to request that the current representation of the target resource be replaced with the provided payload.
  5. The DELETE method is used to delete the specified resource.
  6. The CONNECT method is used to establish a tunnel to the server identified by the target resource.
  7. The OPTIONS method is used to describe the communication options for the target resource.
  8. The TRACE method is used to perform a message loop-back test along the path to the target resource.
  9. The PATCH method is used to apply partial modifications to a resource.

From the introduction of each of these methods, you can see that the HTTP specification gives clear definitions for each method, so when we use them, we should follow these definitions as much as possible, so that we can better collaborate in development.

By understanding these HTTP methods, you can better understand the RESTful API specification because the RESTful API specification is based on these HTTP method specifications to regulate our operations on server resources, while also standardizing URL styles and HTTP status codes.

In RESTful API, the following five HTTP methods are mainly used:

  1. GET: Retrieves a representation of the specified resource on the server.
  2. POST: Creates a resource on the server.
  3. PUT: Updates or replaces the specified resource on the server.
  4. DELETE: Deletes the specified resource on the server.
  5. PATCH: Updates/modifies part of a resource.

In the RESTful API specification, the above HTTP methods represent operations on server resources, which are represented by specific URLs.

Now let me help you better understand RESTful API through some examples:

HTTP GET https://www.flysnow.org/users

HTTP GET https://www.flysnow.org/users/123

The above are two examples of GET methods:

  • The first one represents retrieving information for all users.
  • The second one represents retrieving information for the user with ID 123.

Here’s an example of a POST method:

HTTP POST https://www.flysnow.org/users

This example represents creating a user by providing all the necessary information to the server with the POST method.

Note: Here, “users” is in plural form.

Now that you know how to create a user, how would you update a specific user? It’s actually very simple. Here’s an example code:

HTTP PUT https://www.flysnow.org/users/123

This represents updating/replacing the user with ID 123. When updating, all the user information needed for updating will be provided via the PUT method. The difference between PUT and POST methods here is that, from the URL, the PUT method operates on a single resource, such as the user with ID 123 here.

Quick Tip: If you only want to update part of a user’s information, PATCH method is more appropriate.

By now, I believe you already know how to delete a user. Here’s an example code:

HTTP DELETE https://www.flysnow.org/users/123

The use of DELETE method is the same as PUT method. It also operates on a single resource. Here, it is used to delete the user with ID 123.

A Simple RESTful API #

I believe you now have a good understanding of what RESTful API is. Now, I will guide you through an example of implementing a RESTful API-style application using Golang, to deepen your understanding of RESTful API.

One of the biggest advantages of Go language is that it makes it easy to develop backend network services, which are fast and efficient. When developing backend HTTP network application services, we need to handle many HTTP requests, such as the common RESTful API services, which require handling many requests and returning processed information to the users. For such requirements, Golang provides the built-in net/http package to help us handle these HTTP requests, making it relatively easy to develop an HTTP service.

Now let’s take a look at the implementation of a simple HTTP service in Go language. The code is as follows:

ch21/main.go

func main() {
    http.HandleFunc("/users", handleUsers)
    http.ListenAndServe(":8080", nil)
}

func handleUsers(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "ID:1,Name:张三")
    fmt.Fprintln(w, "ID:2,Name:李四")
    fmt.Fprintln(w, "ID:3,Name:王五")
}
}

After running this example, you can see the following content information by entering http://localhost:8080/users in your browser:

ID:1,Name:张三

ID:2,Name:李四

ID:3,Name:王五

This is not a RESTful API because users can not only get all user information using the HTTP GET method, but also use other HTTP methods such as POST, DELETE, and PUT to get all user information, which clearly does not comply with the specification of a RESTful API.

Now I will modify the above example to make it compliant with the RESTful API specification. The modified example code is as follows:

func handleUsers(w http.ResponseWriter, r *http.Request){

  switch r.Method {

  case "GET":

    w.WriteHeader(http.StatusOK)

    fmt.Fprintln(w,"ID:1,Name:张三")

    fmt.Fprintln(w,"ID:2,Name:李四")

    fmt.Fprintln(w,"ID:3,Name:王五")

  default:

    w.WriteHeader(http.StatusNotFound)

    fmt.Fprintln(w,"not found")

  }

}

Here, I only modified the handleUsers function and added a condition to get all user information only when using the GET method. In other cases, it will return “not found”.

Now, when running this example again, you will find that you can only access it using the HTTP GET method, and if you use other methods, it will display “not found”.

RESTful JSON API #

The most common format for transmitting information in projects is using JSON, which means that the RESTful API we provide should return JSON content to the users.

Using the above example, I have modified it to return JSON content. The modified example code is as follows:

// Data source, similar to data in MySQL
var users = []User{
   {ID: 1,Name: "张三"},
   {ID: 2,Name: "李四"},
   {ID: 3,Name: "王五"},
}

func handleUsers(w http.ResponseWriter, r *http.Request){

  switch r.Method {

  case "GET":

    users,err:=json.Marshal(users)

    if err!=nil {

      w.WriteHeader(http.StatusInternalServerError)

      fmt.Fprint(w,"{\"message\": \""+err.Error()+"\"}")

    }else {

      w.WriteHeader(http.StatusOK)

      w.Write(users)

    }

  default:

    w.WriteHeader(http.StatusNotFound)

    fmt.Fprint(w,"{\"message\": \"not found\"}")

  }

}

// User struct
type User struct {
  ID int
  Name string
}

From the code above, you can see that this time, I created a User structure and used the users slice to store all the users. Then, in the handleUsers function, I converted it into a JSON array and returned it. This way, we have achieved a RESTful API based on the JSON data format. To run this example, enter http://localhost:8080/users in your browser, and you will see the following information:

[{"ID":1,"Name":"张三"},{"ID":2,"Name":"李四"},{"ID":3,"Name":"王五"}]

This is the user information in JSON format, which includes all the users.

Gin Framework #

Although Go’s built-in net/http package makes it relatively easy to create HTTP services, it also has many shortcomings:

  • It is not easy to register specific handling functions for different request methods (POST, GET, etc.);
  • It does not support path variables;
  • It does not automatically correct paths;
  • Its performance is average;
  • It lacks scalability;

Based on these shortcomings, many Go web frameworks have emerged, such as Mux, Gin, Fiber, etc. Today, I’m going to introduce Gin, the most widely used framework.

Importing the Gin Framework #

Gin is a web framework open-sourced on GitHub that encapsulates many commonly used functions for web development and is highly performant. You can use Go module to import it. I have detailed how to import third-party modules in Lesson 18, so I’ll briefly review it here.

First, you need to download and install the Gin framework using the following command:

$ go get -u github.com/gin-gonic/gin

Then, you can import and use it in your Go code:

import "github.com/gin-gonic/gin"

By installing and importing the framework using these steps, you can use the Gin framework in your Go projects.

Using the Gin Framework #

Now that you have imported the Gin framework, I will use it to rewrite the example above. The modified code is as follows:

ch21/main.go

func main() {
   r := gin.Default()
   r.GET("/users", listUser)
   r.Run(":8080")
}

func listUser(c *gin.Context) {
   c.JSON(200, users)
}

Compared to the net/http package, the code for the Gin framework is very simple. You can create a service that only handles HTTP GET requests using its GET method. Outputting data in JSON format is also very simple using the c.JSON method.

Finally, use the Run method to start the HTTP service and listen on port 8080. Now, run this example and enter http://localhost:8080/users in your browser. You will see the same information as when implemented using the net/http package.

Getting Specific Users #

Now that you know how to use the Gin framework to create a simple RESTful API and return all user information, how do you get the information of a specific user?

As we know, to get the information of a specific user, we need to use the GET method and the URL format is as follows:

http://localhost:8080/users/2

In the above example, 2 is the user’s ID, which is used to retrieve the information of a specific user.

Next, I will demonstrate how to implement this functionality using path parameters in the Gin framework. The example code is as follows:

ch21/main.go

func main() {
   // omitting unchanged code
   r.GET("/users/:id", getUser)
}

func getUser(c *gin.Context) {
   id := c.Param("id")
   var user User
   found := false
   // similar to a SQL query in a database
   for _, u := range users {
      if strings.EqualFold(id, strconv.Itoa(u.ID)) {
         user = u
         found = true
        break
      }
    }

    if found {
      c.JSON(200, user)
    } else {
      c.JSON(404, gin.H{
        "message": "User does not exist",
      })
    }
  }
}

In the Gin framework, colons are used in the path to represent path parameters, such as :id in the example. In the getUser function, the ID value of the user to be queried can be obtained by using c.Param("id").

> Tip: The parameter passed to the Param method must match the path parameter, such as the ID in the example.

Now let's run this example and access it through a browser at http://localhost:8080/users/2. This will retrieve the user with the ID of 2 and the output will be as follows:

```json
{"ID":2,"Name":"李四"}

As you can see, we have successfully retrieved the user with the ID of 2, whose name is 李四 (Li Si in English).

What happens if we access an ID that doesn’t exist? For example, 99. The output will be as follows:

➜ curl http://localhost:8080/users/99

{"message":"User does not exist"}%

As shown in the example output above, it returns the message “User does not exist”, which matches the logic in our code.

Adding a new user #

Now that you know how to retrieve all users and retrieve specific users using Gin, you should also know how to add a new user. Let me show you how I use Gin to add a new user and see if it matches your solution.

According to the RESTful API specification, adding a new user is done using the POST method and the URL format is http://localhost:8080/users. Sending data to this URL will create a new user and return the created user information.

Now I will show you how to add a new user using the Gin framework. The example code is as follows:

func main() {
  // omitted unchanged code

  r.POST("/users", createUser)
}

func createUser(c *gin.Context) {
  name := c.DefaultPostForm("name", "")

  if name != "" {
    u := User{ID: len(users) + 1, Name: name}
    users = append(users, u)
    c.JSON(http.StatusCreated,u)
  } else {
    c.JSON(http.StatusOK, gin.H{
      "message": "Please enter a user name",
    })
  }
}

In the above code, the main logic for adding a user is to retrieve the name value uploaded by the client, create a new user with the name, and then store it in the users collection to add a new user.

In this example, the POST method is used to add a user, so it can only be successful if the request uses the POST method.

Now let’s run this example and use the following command to send a request to add a new user and see the result:

➜ curl -X POST -d 'name=飞雪' http://localhost:8080/users

{"ID":4,"Name":"飞雪"}

As you can see, the new user is successfully added and the response includes the user ID assigned to the user.

Summary #

Go provides a powerful SDK that allows us to easily develop network service applications. Using third-party web frameworks like Gin makes it even easier and more efficient. In this article, we have learned how to use the Gin framework to develop RESTful APIs. For more information on the Gin framework, you can refer to the “Golang Gin实战” (Golang Gin Practical Guide) series of articles.

When developing projects, we should make good use of existing tools to make development more efficient and easier to implement. go语言金句.png In project development, we often need to handle CRUD operations (Create, Read, Update, Delete). So far, you have learned how to create and read data. Now I will give you 2 tasks to choose from:

  1. Modify the name of a user.
  2. Delete a user.

In the next lesson, which is the last lesson of this series, I will introduce how to use Go to implement RPC services. Remember to tune in!