REST methods (POST, PUT, DELETE)

7 minutes read

You have already learned about the Gin framework and its routing capabilities. In this topic, we will explore how to work with other HTTP methods like POST, PUT, and DELETE using the Gin framework.

Binding request body

First, let's explore Gin methods that bind data from a request to Go structures. Gin provides various binding methods to extract data from an HTTP request, such as path or query parameters, form data, and body payloads in various formats. Let's start with a request body.

The key binding methods in Gin include:

  • The Bind method binds the data in a request body to the specified struct. It automatically handles the validation and binding based on the Content-Type header. For example, if the header is application/json, the data will decode as JSON, or if the header is application/xml, the data will be treated as XML. If there's an error during binding, such as if the JSON data doesn't match the structure of the Go struct, it automatically writes a 400 Bad Request status code to the HTTP response. Here's an example using Bind:

    taskGroup.POST("", func(c *gin.Context) {
        var task Task
        if err := c.Bind(&task); err != nil {
            // Gin automatically handles the error response
            return
        }
    
        // Process the new task
    })
  • The ShouldBind method is slightly more versatile. It binds the data to the specified struct like Bind and also returns an error. The main difference is that it lets you inspect the error and handle it in a custom way. Also, in case of an error during binding, Gin will not automatically write the status code for the client; it's up to you to decide how to manage the error. Example using ShouldBind:

    taskGroup.POST("", func(c *gin.Context) {
        var task Task
        if err := c.ShouldBindJSON(&task); err != nil {
            // Handle the error in a custom way
            c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
            return
        }
    
        // Process the new task
    })

In summary, the key difference between Bind and ShouldBind in Gin is how they manage errors during binding. Bind automatically sets the status code to StatusBadRequest, while ShouldBind lets you handle errors more personally. The choice between the two depends on your specific requirements and how much control you want over the error-handling process.

Aside from these methods to bind data from a request body, Gin provides more specialized methods, like ShouldBindJSON , ShouldBindXML or ShouldBindYAML. You can find other binding methods in the Context documentation.

Binding request parameters

Sometimes you might need to extract data not only from the request body but also from other parts of the request, for instance, from a URL path or request headers.

URI parameters are variables embedded in the path of a URL. For example, in the URL /users/:id, :id is a URI parameter that you can bind and use in your Gin handlers. To bind URI parameters to a Go struct, you need to add struct tags like `uri:"some_name"` :

type TaskBinding struct {
    ID        int    `uri:"id"`
    Parameter string `uri:"parameter"`
}

func handleURI(c *gin.Context) {
    var task TaskBinding
    if err := c.ShouldBindUri(&task); err != nil {
        log.Println(err)
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    log.Println("task:", task)

    c.JSON(http.StatusOK, task)
}

POST

Now, let's create a POST handler method and use the binding methods we just learned. The POST method in RESTful APIs is employed to submit data to a specified resource, usually to create a new resource on the server. This method is fundamental for scenarios where clients need to send information to the server for processing and storage.

First, let's define a structure for the task data:

type Task struct {
    ID     int    `json:"id"`
    Title  string `json:"title"`
    Status string `json:"status"`
}

The next step would be to create a group and a route for the handler:

taskGroup := r.Group("/task")
taskGroup.POST("", func(c *gin.Context) {
    var task Task

    if err := c.ShouldBind(&task); err != nil {
        // Handle the error in a custom way
        log.Println(err)
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    // Process the new task
    newTaskID, err := db.CreateTask(c.Request.Context(), &task)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusCreated, newTaskID)
})

PUT

The PUT method in RESTful APIs serves to update a resource or create it if it does not exist. This method is essential for modifying existing data on the server or ensuring the creation of a resource with a specific identifier. Let's learn how to create a PUT handler using Gin.

At first, we need to make a path for our endpoint. Since we want to update a task, we need to add an :id parameter to the path. To get a parameter from a path, we need to use the method Param(key string).

Next, we need to read the request body data with new task information. Hence, the handler looks like this:

taskGroup.PUT("/:id", func(c *gin.Context) {
    id := c.Param("id")
    var task Task

    // Bind the JSON body to the task
    if err := c.ShouldBindJSON(&task); err != nil {
        // Handle the error in a custom way
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    // Process the task with the specified id
    if err := db.UpdateTask(c.Request.Context(), id, &task); err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, id)
})

DELETE

The DELETE method in RESTful APIs is used to request the removal or deletion of a specified resource on the server. This method plays a crucial role in managing the lifecycle of resources, allowing clients to indicate that a particular resource should no longer exist.

taskGroup.DELETE("/:id", func(c *gin.Context) {
    id := c.Param("id")

    // Delete the task from the database
    if err := db.DeleteTask(c.Request.Context(), id); err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }

    c.JSON(http.StatusOK, id)
})

Usually, all you need is an ID of the entity. To get an ID, we define a path with a parameter name :id in it, and we use Param method to retrieve it.

Conclusion

We implemented basic CRUD operations using the HTTP methods POST, PUT, and DELETE. Understanding RESTful methods is crucial for designing and interacting with APIs in a consistent and efficient manner. The standardization provided by the HTTP methods in RESTful APIs simplifies communication between clients and servers, promoting scalability and maintainability. Whether you're retrieving, creating, updating, or deleting data, RESTful methods provide a clear and universal approach to handling resources in web applications.

How did you like the theory?
Report a typo