Lesson 4
Handling POST Requests with Gin
Introduction and Overview

Welcome to the first lesson of this course, where we will explore how to add new items to a ToDo app using the HTTP POST request. This is an essential skill for any web developer, as it allows you to send data to a server and update data stores dynamically. In web applications, HTTP methods are crucial as they define how you interact with the server. Among these methods, the POST request is commonly used for sending data, unlike GET requests, which are primarily used for retrieving data. Understanding how to implement POST requests will enable you to build features that allow users to input and manage their own data on your application. Let’s get started!

Understanding HTTP Requests

The HTTP POST request is a key concept for sending data to a server. It allows you to create a new resource on the server, such as a new list item in a ToDo app. Unlike GET requests, which retrieve data, POST requests are used to send data. This is similar to filling out a form and submitting it: the server receives the data, processes it, and usually returns a response to confirm successful data handling. In contrast, an HTTP PUT request is used to update an existing resource or create a new one if it does not exist by completely replacing the target resource with the new request data. The PUT method is idempotent, meaning that making multiple identical requests has the same effect as making a single request. On the other hand, the POST method is not idempotent, as repeating the same operation could create duplicates. Finally, the DELETE request is used to remove a specific resource from the server. Understanding these HTTP methods is essential in the context of our ToDo app because it allows users to not only add new tasks to their list by sending structured data to the server, typically in JSON format, but also update or remove them as needed.

Handling JSON Data in Gin

JSON is a common format for encoding data in web services, and Gin provides helpful methods for parsing JSON data. When a POST request is made to our server, we must parse the incoming JSON to extract user-sent data. In Gin, you can use the ShouldBindJSON method:

Go
1router.POST("/api/todos", func(c *gin.Context) { 2 var newTodo models.Todo 3 if err := c.ShouldBindJSON(&newTodo); err != nil { 4 c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid JSON"}) 5 return 6 } 7 // Handle valid JSON 8})

In this snippet, ShouldBindJSON attempts to bind the incoming JSON payload to the newTodo object. If it encounters an error due to invalid JSON, it immediately responds with a Bad Request status and an error message.

Implementing the POST Route to Add a New ToDo Item

The core of our application is handling the incoming POST request to successfully add a new item to the ToDo list. Let’s go through the implementation:

router.go:

Go
1package router 2 3import ( 4 "net/http" 5 6 "github.com/gin-gonic/gin" 7 8 "codesignal.com/todoapp/controllers" 9) 10 11func SetupRouter() *gin.Engine { 12 router := gin.Default() 13 router.POST("/api/todos", controllers.AddTodo) 14 return router 15}

controllers.go:

Go
1package controllers 2 3import ( 4 "net/http" 5 6 "github.com/gin-gonic/gin" 7 8 "codesignal.com/todoapp/services" 9) 10 11func AddTodo(c *gin.Context) { 12 var newTodo models.Todo 13 if err := c.ShouldBindJSON(&newTodo); err != nil { 14 c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid JSON"}) 15 return 16 } 17 addedTodo := services.AddTodoItem(&newTodo) 18 c.JSON(http.StatusCreated, addedTodo) 19}

services.go:

Go
1package services 2 3import ( 4 "codesignal.com/todoapp/models" 5) 6 7// Todo list acting as an in-memory database 8var todos []models.Todo 9 10func AddTodoItem(newTodo *models.Todo) *models.Todo { 11 newTodo.ID = len(todos) + 1 12 newTodo.Completed = false 13 todos = append(todos, *newTodo) 14 return newTodo 15}

Here’s a breakdown of the process:

  1. Route Definition: The /api/todos endpoint is defined and associated with the AddTodo handler function in the router setup file.
  2. Handler Function: The AddTodo handler function is responsible for orchestrating the flow. It first binds the JSON from the request to a Todo object using bindJSON. If there's an error, it responds with a Bad Request status.
  3. Business Logic: The valid Todo object is passed to the AddTodoItem function in the services package. This function handles the core business logic:
    • Generate ID: A new ID is assigned based on the current length of the todos slice.
    • Set Completion: The Completed field is initialized to false.
    • Append to List: The new ToDo item is appended to the in-memory list of todos.
  4. Response: After successfully adding the item via business logic, the handler function returns a JSON response with the newly created item and a Created status. If custom headers are required, they are added before this response is constructed.
Adding Custom Headers to the Response

In some scenarios, you may want to add custom headers to your HTTP response to provide additional context or metadata. Custom headers can be useful for various purposes, such as specifying content type information or other types of policies. For example, you might add a RateLimit-Remaining header to inform clients of their remaining request quota. Headers are added before calling c.JSON because once c.JSON is invoked, the response is finalized, and headers can no longer be modified. Adding a header involves using the c.Header method before sending a response, as shown in the following snippet:

Go
1package controllers 2 3import ( 4 "net/http" 5 6 "github.com/gin-gonic/gin" 7 8 "codesignal.com/todoapp/services" 9) 10 11func AddTodoWithHeader(c *gin.Context) { 12 var newTodo models.Todo 13 if err := c.ShouldBindJSON(&newTodo); err != nil { 14 c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid JSON"}) 15 return 16 } 17 addedTodo := services.AddTodoItem(&newTodo) 18 c.Header("X-Custom-Header", "CustomValue") // Adding custom header to response 19 c.JSON(http.StatusCreated, addedTodo) 20}

In this implementation, a custom header X-Custom-Header with a value of CustomValue is added to the response. Custom headers can enhance the request-response lifecycle by providing extra information, which can be utilized by clients for various integrations or analytics purposes.

Practical Example: Adding a New ToDo Item

Let's illustrate how the complete process works with an example. Consider a scenario where a user wants to add the task "Learn Go" to their list. The client would send a POST request with JSON payload, for example using curl:

Bash
1curl -X POST http://localhost:8080/api/todos -H "Content-Type: application/json" -d '{"title": "Learn Go"}'

After the server processes the incoming data, the new ToDo item is successfully added, and the server confirms its addition by responding with the completed object:

JSON
1{ 2 "id": 1, 3 "title": "Learn Go", 4 "completed": false 5}
Summary and Next Steps

In this lesson, we explored the functionality of HTTP POST requests and how they play a key role in adding new items to our ToDo app. By understanding and implementing POST requests using Gin, you gained insight into how web applications can dynamically update data stores. We've successfully handled JSON data and implemented a POST route, also employing a separation of concerns — all fundamental steps in creating a fully functional ToDo application. As you move onto the practice exercises, you'll have the opportunity to apply these concepts and solidify your understanding by adding your own ToDo items. Congratulations on reaching this milestone in learning Go web development!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.