Gin Wildcard Routes
When developing web applications with Gin, you'll often need to handle URLs with variable components. This is where wildcard routes come into play - they allow your application to respond dynamically to URLs that match certain patterns rather than exact paths. This tutorial will guide you through using wildcard routes in the Gin framework.
What are Wildcard Routes?
Wildcard routes (also called pattern matching routes) let you define URL patterns that can match multiple actual URLs. For instance, a wildcard route can help you handle:
- All profile pages (
/users/alice
,/users/bob
, etc.) - All blog posts (
/blog/first-post
,/blog/how-to-code
, etc.) - Different API versions (
/api/v1/users
,/api/v2/users
, etc.)
Basic Wildcard Parameters in Gin
In Gin, you can define wildcard parameters using the colon :
prefix followed by a parameter name.
Simple Parameter Example
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
router := gin.Default()
// Route with a parameter
router.GET("/users/:name", func(c *gin.Context) {
name := c.Param("name")
c.JSON(http.StatusOK, gin.H{
"message": "Hello, " + name + "!",
})
})
router.Run(":8080")
}
When you run this server and make a request to /users/alice
, you'll get:
{
"message": "Hello, alice!"
}
Similarly, a request to /users/bob
returns:
{
"message": "Hello, bob!"
}
The :name
part captures any value at that position in the URL and makes it available through the c.Param("name")
method.
Multiple Parameters
You can use multiple parameters in a single route:
router.GET("/books/:category/:id", func(c *gin.Context) {
category := c.Param("category")
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{
"category": category,
"id": id,
})
})
A request to /books/fiction/123
would return:
{
"category": "fiction",
"id": "123"
}
Catch-All Parameters
Sometimes you need to match all possible paths after a certain point. Gin provides a catch-all parameter using the *
symbol:
router.GET("/files/*filepath", func(c *gin.Context) {
filepath := c.Param("filepath")
// Note: filepath will include the leading "/"
c.JSON(http.StatusOK, gin.H{
"file": filepath,
})
})
A request to /files/documents/report.pdf
would return:
{
"file": "/documents/report.pdf"
}
The *filepath
pattern will match anything after /files/
, including additional slashes and subdirectories.
Practical Example: RESTful API
Let's create a more practical example of a simple API for managing articles:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
"strconv"
)
func main() {
router := gin.Default()
// Group for API routes
api := router.Group("/api")
{
// API version group
v1 := api.Group("/v1")
{
articles := v1.Group("/articles")
{
// List all articles
articles.GET("/", listArticles)
// Get a specific article
articles.GET("/:id", getArticle)
// Get all comments for an article
articles.GET("/:id/comments", getArticleComments)
// Get a specific comment for an article
articles.GET("/:articleId/comments/:commentId", getSpecificComment)
}
}
}
router.Run(":8080")
}
func listArticles(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"action": "list all articles",
})
}
func getArticle(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{
"action": "get article",
"id": id,
})
}
func getArticleComments(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{
"action": "get all comments for article",
"articleId": id,
})
}
func getSpecificComment(c *gin.Context) {
articleId := c.Param("articleId")
commentId := c.Param("commentId")
c.JSON(http.StatusOK, gin.H{
"action": "get specific comment",
"articleId": articleId,
"commentId": commentId,
})
}
This example shows how to structure a typical RESTful API with nested resources using wildcard routes.
Handling Query Parameters
It's important to distinguish between route parameters (like :id
) and query parameters (like ?page=1
). You can use both in combination:
router.GET("/search/:category", func(c *gin.Context) {
category := c.Param("category")
query := c.Query("q") // Get the "q" query parameter
page := c.DefaultQuery("page", "1") // Get "page" with default value "1"
c.JSON(http.StatusOK, gin.H{
"category": category,
"query": query,
"page": page,
})
})
A request to /search/books?q=golang&page=2
would return:
{
"category": "books",
"query": "golang",
"page": "2"
}
Route Parameter Validation
While Gin doesn't provide built-in parameter validation in routes, you can implement your own validation in the handler functions:
router.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
// Validate that id is a number
userId, err := strconv.Atoi(id)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "User ID must be a number",
})
return
}
c.JSON(http.StatusOK, gin.H{
"userId": userId,
})
})
Advanced Example: Dynamic API with Versioning
Here's a more advanced example showing how to handle multiple API versions with wildcard routes:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
"strings"
)
func main() {
router := gin.Default()
// Handle any API version
router.GET("/api/:version/*action", handleAPI)
router.Run(":8080")
}
func handleAPI(c *gin.Context) {
version := c.Param("version")
action := c.Param("action")
// Remove the leading slash from action
action = strings.TrimPrefix(action, "/")
// Check valid versions
validVersions := map[string]bool{
"v1": true,
"v2": true,
}
if !validVersions[version] {
c.JSON(http.StatusNotFound, gin.H{
"error": "API version not supported",
})
return
}
c.JSON(http.StatusOK, gin.H{
"version": version,
"action": action,
"message": "API request processed successfully",
})
}
This handler will respond to any API path like /api/v1/users
or /api/v2/products/123
, extracting the version and the remaining path.
Route Priority in Gin
When working with wildcard routes, understanding route priority is essential. Gin matches routes in the order of:
- Static routes (e.g.,
/users/all
) - Parameter routes (e.g.,
/users/:id
) - Catch-all routes (e.g.,
/users/*path
)
For example, with these routes defined:
router.GET("/users/profile", specificHandler)
router.GET("/users/:id", userHandler)
router.GET("/users/*path", catchAllHandler)
A request to /users/profile
will match the first handler, not the others.
Summary
Wildcard routes in Gin provide a flexible way to handle dynamic URLs in your web applications. Here's what we've covered:
- Basic parameter routes with
:param
syntax - Catch-all parameters with
*param
syntax - Using multiple parameters in a single route
- Combining route parameters with query parameters
- Route priority and handling
By using wildcard routes effectively, you can build clean, RESTful APIs and flexible web applications with the Gin framework.
Further Exercises
- Build a simple blog API that uses wildcard routes to handle posts, categories, and tags
- Create a file server that uses catch-all parameters to serve files from different directories
- Implement a URL shortener service that uses parameter routes to redirect short URLs to their full versions
- Add parameter validation for a user management API that ensures IDs are valid numbers
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)