Lesson 3
Handling GET Requests with Gin
Introduction

In this lesson, you will learn how to use GET requests to interact with data in your web application built with Go and the Gin framework. This is a fundamental aspect of creating dynamic web applications, allowing you to retrieve and display data stored on a server.

Understanding GET Requests

GET requests are one of the most common types of HTTP requests used to retrieve data from a server. In the context of web development, they allow clients to access data stored on a server without making any modifications. Imagine GET requests as looking for a specific book in a library — you know it's there, you just need to find and retrieve it.

Defining Data Structures for ToDo Items

To manage tasks within your application, you will define a data structure called Todo. Here's how you can declare it in Go:

Go
1package main 2 3type Todo struct { 4 ID int `json:"id"` 5 Title string `json:"title"` 6 Completed bool `json:"completed"` 7}

In this structure:

  • ID is an integer that uniquely identifies each task.
  • Title is a string representing the task description.
  • Completed is a boolean indicating if a task is finished.

The notations like json:"id" are known as struct tags. They control the JSON serialization process, allowing you to manage how the struct fields appear when encoded into or decoded from JSON. Not only do they define the name of the field in the JSON data (e.g., ID as "id", Title as "title", and Completed as "completed"), but they also allow control over visibility and behavior when the field is empty. If JSON encoding is not specified using struct tags, the default JSON encoding in Go uses the exact field names as they are defined in the struct, including the capitalization.

In Go, fields must be exported, meaning they need to be in CapitalCase, to be accessible for serialization. CapitalCase fields are considered "public," while camelCase fields are "private" and not serialized. Therefore, ensuring fields like ID, Title, and Completed are in CapitalCase is crucial for them to be included in the JSON output.

Setting Up Routes to Handle GET Requests

Setting up routes in Gin is straightforward. First, we'll register a route to retrieve all tasks in your to-do list:

Go
1package main 2 3import ( 4 "net/http" 5 6 "github.com/gin-gonic/gin" 7) 8 9// Route definition 10func main() { 11 r := gin.Default() 12 r.GET("/api/todos", getTodos) 13 r.Run() 14} 15 16// Handler function 17func getTodos(c *gin.Context) { 18 c.JSON(http.StatusOK, todos) 19}

This example registers a GET route at /api/todos. When a GET request is made to this endpoint, it returns all to-do items in JSON format with an HTTP 200 OK status code. This route helps list all tasks, which is a pivotal feature in any task management application.

Understanding Query and Path Parameters in Gin

Query and path parameters are both ways to pass variables to a specific route in a URL. While path parameters are part of the URL path itself, query parameters are appended to the end of a URL after a ?. For example, in the URL /api/todos/123?completed=true, 123 is a path parameter, and completed=true is a query parameter.

Path parameters are useful for identifying a specific resource, and they are mandatory for the request to be processed. Query parameters, on the other hand, are optional and generally used for filtering or narrowing down the data returned. They provide greater flexibility in terms of what data is retrieved or how it should be formatted.

Retrieving Todos with Query Parameters

We can use query parameters to filter the list of tasks based on conditions. For instance, retrieving only the completed todos:

Go
1package main 2 3import ( 4 "net/http" 5 "strconv" 6 7 "github.com/gin-gonic/gin" 8) 9 10// Route definition 11func main() { 12 r := gin.Default() 13 r.GET("/api/todos", getTodos) 14 r.Run() 15} 16 17// Handler function 18func getTodos(c *gin.Context) { 19 var completed *bool 20 completedParam := c.Query("completed") 21 if completedParam != "" { 22 parsedCompleted, err := strconv.ParseBool(completedParam) 23 if err != nil { 24 c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid query parameter value"}) 25 return 26 } 27 28 completed = &parsedCompleted 29 } 30 31 c.JSON(http.StatusOK, filterTodos(completed)) 32} 33 34// Business Logic 35func filterTodos(completed *bool) []Todo { 36 var filteredTodos []Todo 37 38 if completed == nil { 39 return todos 40 } 41 42 for _, todo := range todos { 43 if todo.Completed == *completed { 44 filteredTodos = append(filteredTodos, todo) 45 } 46 } 47 48 return filteredTodos 49}

In this example:

  • We retrieve the completed status from the query string using c.Query("completed").
  • If the parameter is present, convert it to a boolean using strconv.ParseBool. If conversion fails, respond with a 400 Bad Request error.
  • The handler function getTodos calls filterTodos, passing the converted boolean (or nil) to filter the todos.
  • filterTodos performs logic to return either filtered results or all todos based on the completed status.
Retrieving a Single Todo with Path Parameters

We can employ path parameters to fetch a specific task by its ID:

Go
1package main 2 3import ( 4 "net/http" 5 "strconv" 6 7 "github.com/gin-gonic/gin" 8) 9 10// Route definition 11func main() { 12 r := gin.Default() 13 r.GET("/api/todos/:id", getTodoByID) 14 r.Run() 15} 16 17// Handler function 18func getTodoByID(c *gin.Context) { 19 id, err := strconv.Atoi(c.Param("id")) 20 if err != nil { 21 c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"}) 22 return 23 } 24 c.JSON(http.StatusOK, fetchTodoByID(id)) 25} 26 27// Business Logic 28func fetchTodoByID(id int) gin.H { 29 for _, todo := range todos { 30 if todo.ID == id { 31 return gin.H{"todo": todo} 32 } 33 } 34 return gin.H{"error": "Todo not found"} 35}

In this implementation:

  • The /api/todos/:id is a route pattern where :id acts as a placeholder for capturing a specific value from the URL. For example, 123 in /api/todos/123.
  • We extract the id from the URL path using c.Param("id") and convert it to an integer using strconv.Atoi.
  • If the conversion fails, respond with a 400 Bad Request indicating an invalid ID, ensuring proper error handling.
  • The handler function getTodoByID calls fetchTodoByID to perform logic for finding and returning the specific todo.
Project Directory Structure and Package Organization

To create a maintainable and modular web application, it's essential to organize your project's code into distinct packages that align with its functionality. We'll use a structure that includes the main file located in cmd/todoapp/main.go, complemented by specific packages: router, controllers, services, and models. Each package will contain a corresponding file, ensuring clarity and cohesion.

This structure is beneficial as it encourages a separation of concerns, crucial in software development. Let's briefly discuss the role of each package:

  • Router Package: Defines routes and delegates handling to the appropriate controller (or handler) function.
  • Controllers Package: Manages the handler functions, which take care of tasks such as parsing input and formatting responses.
  • Services Package: Contains the business logic, including processing tasks like querying data sources or applying business rules.
  • Models Package: Defines data structures such as the Todo struct.

By segmenting code into these packages, we define clear boundaries for each component of our application, making it easier to manage, test, and expand features independently. This structured approach not only keeps our code organized but also facilitates collaboration among teams, as each team member can focus on different packages without stepping on each other's toes. By adhering to this organization, we lay a solid foundation for our application's future scalability and maintainability.

Summary and Preparing for Practice

In this lesson, you have learned how to handle GET requests using Go and Gin to build parts of a to-do app. You explored setting up routes, defining data structures, retrieving all tasks, and fetching specific tasks with error handling. These skills will allow you to manage data retrieval in web applications effectively.

Next, you'll have the opportunity to apply what you've learned by working through practice exercises. These exercises are designed to reinforce your understanding, allowing you to implement and expand on the concepts taught in this lesson. Keep pushing forward as you build a more robust to-do application.

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