Skip to main content

Gin Query Parameters

Introduction

When building web applications with Gin, one of the common tasks you'll encounter is extracting and processing query parameters from URLs. Query parameters are the key-value pairs that appear after a question mark (?) in a URL, such as http://example.com/api/users?page=1&limit=10 where page=1 and limit=10 are the query parameters.

In this tutorial, we'll explore how Gin handles query parameters and learn various techniques for extracting, validating, and working with them effectively in your Go web applications.

Understanding Query Parameters

Query parameters allow clients to send additional information to your server as part of an HTTP request. They're widely used for:

  • Pagination (?page=2&limit=20)
  • Filtering (?status=active&category=books)
  • Searching (?q=golang+tutorials)
  • Sorting (?sort=price&order=desc)

Basic Query Parameter Extraction

Gin provides several methods to extract query parameters from the request URL. Let's explore these techniques:

Using c.Query Method

The most straightforward way to get a query parameter value is using the Query method:

go
func GetUsers(c *gin.Context) {
// Extract the 'name' query parameter
name := c.Query("name")

c.JSON(200, gin.H{
"name": name,
})
}

If you access /users?name=john, the response will be:

json
{
"name": "john"
}

If the parameter doesn't exist, c.Query returns an empty string.

Default Values with c.DefaultQuery

When you want to provide a default value for a parameter that might not be present, use DefaultQuery:

go
func GetUsers(c *gin.Context) {
// Use "guest" as the default value if 'name' is not provided
name := c.DefaultQuery("name", "guest")

// Default page number is 1 if not specified
page := c.DefaultQuery("page", "1")

c.JSON(200, gin.H{
"name": name,
"page": page,
})
}

If you access /users (without any query parameters), the response will be:

json
{
"name": "guest",
"page": "1"
}

Checking Parameter Existence with c.GetQuery

Sometimes, you need to know if a parameter was explicitly provided or not. In such cases, use GetQuery:

go
func GetUsers(c *gin.Context) {
name, nameExists := c.GetQuery("name")

if nameExists {
c.JSON(200, gin.H{
"name": name,
"provided": true,
})
} else {
c.JSON(200, gin.H{
"name": "no name provided",
"provided": false,
})
}
}

If you access /users?name=jane, the response will be:

json
{
"name": "jane",
"provided": true
}

But if you access /users without the parameter, you'll get:

json
{
"name": "no name provided",
"provided": false
}

Handling Multiple Values for the Same Parameter

Some query parameters can appear multiple times in a URL, like /search?tag=golang&tag=web. Gin provides a way to access all values:

go
func SearchByTags(c *gin.Context) {
// Get all values for the 'tag' parameter
tags := c.QueryArray("tag")

c.JSON(200, gin.H{
"tags": tags,
})
}

If you access /search?tag=golang&tag=web, the response will be:

json
{
"tags": ["golang", "web"]
}

Parsing Query Parameters into Structs

For complex APIs with many query parameters, parsing them directly into a struct can be cleaner:

go
type SearchParams struct {
Query string `form:"q"`
Page int `form:"page,default=1"`
Limit int `form:"limit,default=20"`
SortBy string `form:"sort_by,default=created_at"`
SortOrder string `form:"sort_order,default=desc"`
Tags []string `form:"tags"`
}

func AdvancedSearch(c *gin.Context) {
var params SearchParams

// Bind query parameters to the struct
if err := c.ShouldBindQuery(&params); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}

c.JSON(200, gin.H{
"search_params": params,
})
}

For a request to /search?q=golang&page=2&tags=api&tags=web, the response would be:

json
{
"search_params": {
"Query": "golang",
"Page": 2,
"Limit": 20,
"SortBy": "created_at",
"SortOrder": "desc",
"Tags": ["api", "web"]
}
}

Query Parameter Validation

For robust applications, you should validate query parameters. Gin works well with the validator package:

go
type ProductQuery struct {
Category string `form:"category" binding:"required"`
MinPrice int `form:"min_price,default=0" binding:"min=0"`
MaxPrice int `form:"max_price,default=1000" binding:"gtefield=MinPrice"`
InStock bool `form:"in_stock,default=false"`
}

func ListProducts(c *gin.Context) {
var query ProductQuery

// Bind and validate in one step
if err := c.ShouldBindQuery(&query); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}

// Process the validated query parameters
c.JSON(200, gin.H{
"query_params": query,
"message": "Valid query parameters received",
})
}

Real-World Example: Implementing Pagination

Let's create a more complete example of implementing pagination using query parameters:

go
package main

import (
"github.com/gin-gonic/gin"
)

// Article represents a blog article
type Article struct {
ID int `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
}

// Pagination contains pagination parameters
type Pagination struct {
Page int `form:"page,default=1" binding:"min=1"`
Limit int `form:"limit,default=10" binding:"min=1,max=100"`
}

// Database simulation
var articles = []Article{
{ID: 1, Title: "Intro to Go", Content: "Go is awesome..."},
{ID: 2, Title: "Gin Framework", Content: "Gin is a web framework..."},
// ... imagine more articles here
}

func getArticles(c *gin.Context) {
var pagination Pagination

if err := c.ShouldBindQuery(&pagination); err != nil {
c.JSON(400, gin.H{"error": "Invalid pagination parameters"})
return
}

// Calculate start and end indices
startIndex := (pagination.Page - 1) * pagination.Limit
endIndex := startIndex + pagination.Limit

// Boundary checks
if startIndex >= len(articles) {
c.JSON(200, gin.H{
"data": []Article{},
"pagination": pagination,
"total": len(articles),
})
return
}

if endIndex > len(articles) {
endIndex = len(articles)
}

// Return paginated results
c.JSON(200, gin.H{
"data": articles[startIndex:endIndex],
"pagination": pagination,
"total": len(articles),
})
}

func main() {
r := gin.Default()
r.GET("/articles", getArticles)
r.Run(":8080")
}

With this implementation, a request to /articles?page=1&limit=5 would return the first 5 articles, while /articles?page=2&limit=5 would return the next 5.

Advanced Filtering Example

Let's implement a more advanced filtering system using query parameters:

go
package main

import (
"strings"
"time"

"github.com/gin-gonic/gin"
)

// Product represents a product item
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
Category string `json:"category"`
CreatedAt time.Time `json:"created_at"`
InStock bool `json:"in_stock"`
}

// ProductFilter defines available filter options
type ProductFilter struct {
Query string `form:"q"`
Categories []string `form:"category"`
MinPrice float64 `form:"min_price,default=0" binding:"min=0"`
MaxPrice float64 `form:"max_price,default=1000000" binding:"gtefield=MinPrice"`
InStock *bool `form:"in_stock"`
SortBy string `form:"sort_by,default=id" binding:"oneof=id name price created_at"`
Order string `form:"order,default=asc" binding:"oneof=asc desc"`
}

// Some sample products
var products = []Product{
{ID: 1, Name: "Laptop", Price: 999.99, Category: "electronics", CreatedAt: time.Now(), InStock: true},
{ID: 2, Name: "T-Shirt", Price: 19.99, Category: "clothing", CreatedAt: time.Now(), InStock: true},
{ID: 3, Name: "Coffee Mug", Price: 8.99, Category: "kitchenware", CreatedAt: time.Now(), InStock: false},
// ... more products
}

func filterProducts(c *gin.Context) {
var filter ProductFilter

if err := c.ShouldBindQuery(&filter); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}

// Apply filters (simplified implementation)
var results []Product

for _, product := range products {
// Filter by search query
if filter.Query != "" && !strings.Contains(strings.ToLower(product.Name), strings.ToLower(filter.Query)) {
continue
}

// Filter by category
if len(filter.Categories) > 0 {
categoryMatched := false
for _, cat := range filter.Categories {
if cat == product.Category {
categoryMatched = true
break
}
}
if !categoryMatched {
continue
}
}

// Filter by price range
if product.Price < filter.MinPrice || product.Price > filter.MaxPrice {
continue
}

// Filter by stock status
if filter.InStock != nil && product.InStock != *filter.InStock {
continue
}

// Product passed all filters
results = append(results, product)
}

// Return filtered results
c.JSON(200, gin.H{
"results": results,
"count": len(results),
"filters": filter,
})
}

func main() {
r := gin.Default()
r.GET("/products", filterProducts)
r.Run(":8080")
}

With this implementation, you can make requests like:

  • /products?q=lap&category=electronics&min_price=500
  • /products?in_stock=true&sort_by=price&order=desc

Summary

In this tutorial, we've covered a comprehensive set of techniques for working with query parameters in Gin:

  • Basic extraction with c.Query() and c.DefaultQuery()
  • Checking parameter existence with c.GetQuery()
  • Handling multiple values with c.QueryArray()
  • Parsing query parameters into structs
  • Validating query parameters
  • Real-world examples for pagination and filtering

Query parameters are a powerful way to make your APIs flexible and user-friendly. By following these best practices, you can build robust web applications that process user input correctly and efficiently.

Additional Resources and Exercises

Further Reading

Exercises

  1. Simple Search API: Create a simple search API that accepts q, limit, and offset parameters and returns filtered results.

  2. Advanced Filtering: Implement an API endpoint that filters a list of users by multiple criteria like age range, location, and status.

  3. Parameter Validation: Create an API that validates complex parameter relationships (e.g., if parameter A is provided, parameter B must also be provided).

  4. URL Shortener with Analytics: Build a URL shortener service that accepts query parameters for tracking referrers and other analytics data.

  5. Combining Path and Query Parameters: Create an API that uses both path parameters (like /api/products/:id) and query parameters (like ?fields=name,price) to return specific fields of a product.

By mastering query parameter handling in Gin, you'll be able to build flexible, powerful APIs that are both developer and user-friendly.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)