Gin Request Structure
When building web applications with Gin, understanding how to access and handle incoming request data is fundamental. In this guide, we'll explore the structure of HTTP requests in Gin and learn how to extract various types of information from them.
Introduction to Request Structure in Gin
In Gin, requests are represented by the *gin.Context
object that's passed to your handler functions. This context contains all the information about the incoming HTTP request and provides methods to access parameters, headers, body content, and more.
The request structure can be divided into several components:
- URL Parameters
- Query Parameters
- Form Data
- Request Body (JSON, XML, etc.)
- Headers
- Files
- Cookies
Let's dive deeper into each of these components and see how to access them in your Gin applications.
URL Parameters
URL parameters (also called route parameters) are parts of the URL path that vary and can be captured as variables.
Basic Usage
router.GET("/users/:id", func(c *gin.Context) {
// Get the 'id' parameter from the URL
id := c.Param("id")
c.JSON(200, gin.H{
"message": "Looking up user",
"id": id,
})
})
If a request is made to /users/123
, the variable id
will contain the value "123"
.
Example: Using URL Parameters for Resource Identification
router.GET("/products/:category/:productId", func(c *gin.Context) {
category := c.Param("category")
productID := c.Param("productId")
c.JSON(200, gin.H{
"category": category,
"productID": productID,
})
})
When accessing /products/electronics/laptop-15
, this would output:
{
"category": "electronics",
"productID": "laptop-15"
}
Query Parameters
Query parameters are the parts after the ?
in a URL.
Basic Usage
// For a URL like /search?q=golang&page=1
router.GET("/search", func(c *gin.Context) {
query := c.Query("q") // Method to get a query parameter
page := c.DefaultQuery("page", "1") // Get with default value if not present
c.JSON(200, gin.H{
"query": query,
"page": page,
})
})
Example: Implementing Search and Pagination
router.GET("/products", func(c *gin.Context) {
search := c.Query("search")
category := c.Query("category")
page := c.DefaultQuery("page", "1")
limit := c.DefaultQuery("limit", "10")
// Convert string parameters to integers
pageInt, _ := strconv.Atoi(page)
limitInt, _ := strconv.Atoi(limit)
// In a real application, you'd query your database here
c.JSON(200, gin.H{
"search": search,
"category": category,
"page": pageInt,
"limit": limitInt,
"results": []string{"Product 1", "Product 2"}, // Example results
})
})
Form Data
Forms are a common way to submit data in web applications. Gin provides convenient methods to access form data.
Basic Usage
router.POST("/form", func(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")
// Use DefaultPostForm to provide a default value
remember := c.DefaultPostForm("remember", "false")
c.JSON(200, gin.H{
"username": username,
"password": "[hidden]",
"remember": remember,
})
})
Handling Form Arrays
Sometimes you might have multiple form values with the same name:
router.POST("/form-array", func(c *gin.Context) {
// For form data like: interests=sports&interests=music&interests=reading
interests := c.PostFormArray("interests")
c.JSON(200, gin.H{
"interests": interests, // This will be a string slice
})
})
Example: Creating a Contact Form Handler
router.POST("/contact", func(c *gin.Context) {
name := c.PostForm("name")
email := c.PostForm("email")
subject := c.PostForm("subject")
message := c.PostForm("message")
// Validate inputs
if name == "" || email == "" || message == "" {
c.JSON(400, gin.H{"error": "Missing required fields"})
return
}
// In a real app, you would save this to a database or send an email
c.JSON(200, gin.H{
"status": "success",
"message": "Contact form submitted successfully",
})
})
JSON Request Body
For API development, JSON is a common format for request payloads. Gin makes it easy to bind JSON data to Go structs.
Basic Usage
// Define a struct to match your expected JSON structure
type LoginRequest struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
router.POST("/login", func(c *gin.Context) {
var login LoginRequest
// Bind the JSON from the request body to the struct
if err := c.ShouldBindJSON(&login); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// Process login (in a real app)
if login.Username == "admin" && login.Password == "password" {
c.JSON(200, gin.H{"status": "logged in successfully"})
} else {
c.JSON(401, gin.H{"status": "unauthorized"})
}
})
Custom Validation with Binding
Gin uses go-playground/validator for request validation. You can add validation tags to your struct fields:
type CreateUserRequest struct {
Username string `json:"username" binding:"required,min=4,max=20"`
Email string `json:"email" binding:"required,email"`
Age int `json:"age" binding:"required,gte=18"`
Password string `json:"password" binding:"required,min=8"`
}
router.POST("/users", func(c *gin.Context) {
var newUser CreateUserRequest
if err := c.ShouldBindJSON(&newUser); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// Create user in database (omitted for brevity)
c.JSON(201, gin.H{
"message": "User created successfully",
"username": newUser.Username,
"email": newUser.Email,
})
})
Request Headers
HTTP headers provide additional information about the request or the client.
Basic Usage
router.GET("/headers", func(c *gin.Context) {
// Get a specific header
userAgent := c.GetHeader("User-Agent")
// Get all headers
headers := c.Request.Header
c.JSON(200, gin.H{
"userAgent": userAgent,
"headers": headers,
})
})
Example: API Authorization with Headers
router.GET("/protected", func(c *gin.Context) {
// Get the Authorization header
authHeader := c.GetHeader("Authorization")
// Check if it's properly formatted (in a real app, you'd validate the token)
if !strings.HasPrefix(authHeader, "Bearer ") {
c.JSON(401, gin.H{"error": "Unauthorized: Invalid or missing token"})
return
}
// Extract the token
token := strings.TrimPrefix(authHeader, "Bearer ")
// In a real app, you'd validate the token here
if token == "secret-token-value" {
c.JSON(200, gin.H{"message": "Access granted to protected resource"})
} else {
c.JSON(401, gin.H{"error": "Unauthorized: Invalid token"})
}
})
File Uploads
Gin makes file uploads straightforward with its built-in methods.
Basic Single File Upload
router.POST("/upload", func(c *gin.Context) {
// Single file
file, err := c.FormFile("file")
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// Save the file
// Note: In production, you should sanitize filenames and use a more secure approach
dst := "./uploads/" + file.Filename
if err := c.SaveUploadedFile(file, dst); err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{
"message": "File uploaded successfully",
"filename": file.Filename,
"size": file.Size,
})
})
Multiple File Upload
router.POST("/upload-multiple", func(c *gin.Context) {
// Multipart form
form, err := c.MultipartForm()
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
files := form.File["files"]
fileInfos := []gin.H{}
for _, file := range files {
// Save each file
dst := "./uploads/" + file.Filename
if err := c.SaveUploadedFile(file, dst); err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
fileInfos = append(fileInfos, gin.H{
"filename": file.Filename,
"size": file.Size,
})
}
c.JSON(200, gin.H{
"message": fmt.Sprintf("%d files uploaded successfully", len(files)),
"files": fileInfos,
})
})
Cookies
Cookies are small pieces of data stored on the client side. Gin provides methods to read and write cookies.
Reading Cookies
router.GET("/cookies", func(c *gin.Context) {
// Get a cookie by name
cookie, err := c.Cookie("user_session")
if err != nil {
cookie = "Not found"
}
c.JSON(200, gin.H{
"user_session": cookie,
})
})
Setting Cookies
router.GET("/set-cookie", func(c *gin.Context) {
// Set a cookie - parameters are (name, value, maxAge, path, domain, secure, httpOnly)
c.SetCookie("user_session", "session123", 3600, "/", "localhost", false, true)
c.JSON(200, gin.H{
"message": "Cookie set successfully",
})
})
Accessing Raw Request Data
Sometimes you might need to access the raw request body or other low-level details.
router.POST("/raw", func(c *gin.Context) {
// Read raw data
data, err := io.ReadAll(c.Request.Body)
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// Get HTTP method and URL
method := c.Request.Method
url := c.Request.URL.String()
c.JSON(200, gin.H{
"method": method,
"url": url,
"rawData": string(data),
})
})
Real-World Example: Building an API Endpoint
Let's put together what we've learned to build a more comprehensive API endpoint:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
"strconv"
)
type Product struct {
ID int `json:"id"`
Name string `json:"name" binding:"required"`
Description string `json:"description"`
Price float64 `json:"price" binding:"required,gt=0"`
Category string `json:"category" binding:"required"`
}
// Mock database
var products = []Product{
{1, "Laptop", "Powerful laptop for developers", 1299.99, "Electronics"},
{2, "Coffee Mug", "Ceramic mug for programmers", 12.99, "Home"},
}
func main() {
r := gin.Default()
// Get all products with optional filtering
r.GET("/products", func(c *gin.Context) {
category := c.Query("category")
minPrice := c.Query("min_price")
results := []Product{}
for _, product := range products {
// Apply category filter if provided
if category != "" && product.Category != category {
continue
}
// Apply price filter if provided
if minPrice != "" {
min, err := strconv.ParseFloat(minPrice, 64)
if err == nil && product.Price < min {
continue
}
}
results = append(results, product)
}
c.JSON(http.StatusOK, results)
})
// Get a specific product
r.GET("/products/:id", func(c *gin.Context) {
idParam := c.Param("id")
id, err := strconv.Atoi(idParam)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid product ID"})
return
}
for _, product := range products {
if product.ID == id {
c.JSON(http.StatusOK, product)
return
}
}
c.JSON(http.StatusNotFound, gin.H{"error": "Product not found"})
})
// Create a new product
r.POST("/products", func(c *gin.Context) {
var newProduct Product
if err := c.ShouldBindJSON(&newProduct); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Generate a new ID (this would be handled by your database in a real app)
maxID := 0
for _, p := range products {
if p.ID > maxID {
maxID = p.ID
}
}
newProduct.ID = maxID + 1
// Add to our "database"
products = append(products, newProduct)
c.JSON(http.StatusCreated, newProduct)
})
r.Run(":8080")
}
This example demonstrates how to:
- Get query parameters for filtering
- Extract URL parameters for specific resources
- Bind JSON to structs with validation
- Return appropriate HTTP status codes
Summary
In this guide, we've explored the various components of HTTP requests in Gin and how to access them:
- URL parameters using
c.Param()
- Query parameters using
c.Query()
andc.DefaultQuery()
- Form data using
c.PostForm()
and related methods - JSON request body using
c.ShouldBindJSON()
- Headers using
c.GetHeader()
- File uploads using
c.FormFile()
andc.MultipartForm()
- Cookies using
c.Cookie()
andc.SetCookie()
- Raw request data by accessing
c.Request
directly
Understanding these request structures is essential for building robust web applications and APIs with Gin. By properly handling the different types of request data, you can create user-friendly interfaces and developer-friendly APIs.
Additional Exercises
- Build a RESTful API for a book collection that supports filtering by author, genre, and publication year
- Create a user registration form that validates email, password strength, and handles profile picture upload
- Implement a search endpoint that combines multiple query parameters and returns paginated results
- Create an authentication system using JWT tokens stored in headers or cookies
Resources
- Official Gin Documentation
- Gin GitHub Repository
- Go-Playground Validator Documentation
- HTTP Request Methods - MDN Web Docs
- Understanding REST APIs
By mastering Gin's request handling capabilities, you'll be well-equipped to build powerful and flexible web applications in Go.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)