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!
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.
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:
Go1router.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.
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
:
Go1package 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
:
Go1package 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
:
Go1package 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:
- Route Definition: The
/api/todos
endpoint is defined and associated with theAddTodo
handler function in the router setup file. - Handler Function: The
AddTodo
handler function is responsible for orchestrating the flow. It first binds the JSON from the request to aTodo
object usingbindJSON
. If there's an error, it responds with aBad Request
status. - Business Logic: The valid
Todo
object is passed to theAddTodoItem
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 tofalse
. - Append to List: The new ToDo item is appended to the in-memory list of todos.
- Generate ID: A new ID is assigned based on the current length of the
- 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.
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:
Go1package 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.
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
:
Bash1curl -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:
JSON1{ 2 "id": 1, 3 "title": "Learn Go", 4 "completed": false 5}
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!