Gin Route Groups
When building web applications with Gin framework, you'll often need to create multiple routes with similar patterns or that share common middleware. This is where route groups come in handy. In this tutorial, we'll explore how to effectively use Gin route groups to organize your API endpoints and make your code more maintainable.
What are Route Groups?
Route groups in Gin allow you to:
- Group multiple routes under a common URL prefix
- Apply middleware to specific sets of routes
- Organize your API endpoints logically
- Create nested route structures
Route groups can dramatically improve code organization and make your API structure more intuitive.
Creating Basic Route Groups
Let's start with a simple example of how to create a route group in Gin:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
router := gin.Default()
// Create a route group for API v1
v1 := router.Group("/v1")
{
v1.GET("/users", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Get all users from v1"})
})
v1.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{"message": "Get user " + id + " from v1"})
})
v1.POST("/users", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Create user in v1"})
})
}
router.Run(":8080")
}
When you run this code, the following endpoints will be available:
GET /v1/users
- Returns all usersGET /v1/users/:id
- Returns a specific user by IDPOST /v1/users
- Creates a new user
All these endpoints share the common /v1
prefix, making it clear they belong to version 1 of your API.
Applying Middleware to Route Groups
One of the most powerful features of route groups is the ability to apply middleware selectively to groups of routes. This allows you to add authentication, logging, or other middleware functions to specific endpoints.
Let's create a simple example with authentication middleware:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
// Auth middleware function
func AuthRequired() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token != "valid-token" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
c.Abort()
return
}
c.Next()
}
}
func main() {
router := gin.Default()
// Public routes
public := router.Group("/public")
{
public.GET("/posts", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Public posts"})
})
}
// Protected routes with auth middleware
protected := router.Group("/admin")
protected.Use(AuthRequired())
{
protected.GET("/users", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Admin users list"})
})
protected.POST("/users", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "User created by admin"})
})
}
router.Run(":8080")
}
In this example:
/public/posts
can be accessed by anyone/admin/users
requires a valid authentication token in the Authorization header
You can test these endpoints with curl:
For public endpoint:
curl http://localhost:8080/public/posts
For protected endpoint (will fail without token):
curl http://localhost:8080/admin/users
For protected endpoint with token:
curl -H "Authorization: valid-token" http://localhost:8080/admin/users
Nesting Route Groups
Gin allows you to create nested route groups, which can be useful for organizing complex APIs with multiple resource types and versions.
Here's an example of nested route groups:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
router := gin.Default()
// API group
api := router.Group("/api")
{
// V1 group inside API group
v1 := api.Group("/v1")
{
// Users endpoints in V1
users := v1.Group("/users")
{
users.GET("", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Get all users from API v1"})
})
users.GET("/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{"message": "Get user " + id + " from API v1"})
})
users.POST("", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Create user in API v1"})
})
}
// Products endpoints in V1
products := v1.Group("/products")
{
products.GET("", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Get all products from API v1"})
})
}
}
// V2 group inside API group
v2 := api.Group("/v2")
{
v2.GET("/users", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Get all users from API v2"})
})
}
}
router.Run(":8080")
}
This creates the following endpoints:
GET /api/v1/users
- Get all users (API v1)GET /api/v1/users/:id
- Get specific user (API v1)POST /api/v1/users
- Create user (API v1)GET /api/v1/products
- Get all products (API v1)GET /api/v2/users
- Get all users (API v2)
Nesting route groups helps maintain a clean, hierarchical structure for your API endpoints.
Real-World Example: Building a REST API
Let's create a more practical example of how route groups can be used in a RESTful API for a blog application:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
// Middleware functions
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
// Log request details
println("Request:", c.Request.Method, c.Request.URL.Path)
c.Next()
}
}
func Authentication() gin.HandlerFunc {
return func(c *gin.Context) {
// Check authentication
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"})
c.Abort()
return
}
c.Next()
}
}
func AdminRequired() gin.HandlerFunc {
return func(c *gin.Context) {
// Check if user is admin
role := c.GetHeader("User-Role")
if role != "admin" {
c.JSON(http.StatusForbidden, gin.H{"error": "Admin rights required"})
c.Abort()
return
}
c.Next()
}
}
func main() {
router := gin.New()
router.Use(Logger())
// API routes
api := router.Group("/api")
{
// Public routes
public := api.Group("/public")
{
public.GET("/posts", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "List of public posts"})
})
public.GET("/posts/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{"message": "Viewing post " + id})
})
}
// User routes (require authentication)
user := api.Group("/user")
user.Use(Authentication())
{
user.GET("/profile", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "User profile"})
})
user.POST("/posts", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Post created"})
})
user.PUT("/posts/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{"message": "Post " + id + " updated"})
})
}
// Admin routes (require authentication and admin role)
admin := api.Group("/admin")
admin.Use(Authentication(), AdminRequired())
{
admin.GET("/users", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "List of all users"})
})
admin.DELETE("/users/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{"message": "User " + id + " deleted"})
})
}
}
router.Run(":8080")
}
This example demonstrates several advanced concepts:
- Global middleware (
Logger
) applied to all routes - Different middleware stacks for different route groups:
- Public routes: only logging
- User routes: logging + authentication
- Admin routes: logging + authentication + admin role check
- RESTful organization of endpoints
Best Practices for Route Groups
When working with Gin route groups, consider these best practices:
-
Logical organization: Group routes based on resource types, authentication requirements, or API versions.
-
Middleware chaining: Apply middleware in order from least to most specific.
-
Separation of concerns: Consider moving route definitions to separate files when your application grows.
-
Consistent naming: Use consistent naming conventions for your routes and endpoints.
-
Documentation: Comment your route groups to make the structure clear for other developers.
Here's a modular approach example:
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
// Register route groups from different modules
setupAuthRoutes(router)
setupUserRoutes(router)
setupProductRoutes(router)
router.Run(":8080")
}
func setupAuthRoutes(router *gin.Engine) {
auth := router.Group("/auth")
{
auth.POST("/login", loginHandler)
auth.POST("/register", registerHandler)
auth.POST("/forgot-password", forgotPasswordHandler)
}
}
func setupUserRoutes(router *gin.Engine) {
users := router.Group("/users")
users.Use(authMiddleware())
{
users.GET("", getAllUsersHandler)
users.GET("/:id", getUserHandler)
users.PUT("/:id", updateUserHandler)
users.DELETE("/:id", deleteUserHandler)
}
}
func setupProductRoutes(router *gin.Engine) {
products := router.Group("/products")
{
products.GET("", getAllProductsHandler)
products.GET("/:id", getProductHandler)
// Admin only routes
admin := products.Group("")
admin.Use(adminMiddleware())
{
admin.POST("", createProductHandler)
admin.PUT("/:id", updateProductHandler)
admin.DELETE("/:id", deleteProductHandler)
}
}
}
// Handler and middleware function definitions would go here
func loginHandler(c *gin.Context) {}
func registerHandler(c *gin.Context) {}
func forgotPasswordHandler(c *gin.Context) {}
func getAllUsersHandler(c *gin.Context) {}
func getUserHandler(c *gin.Context) {}
func updateUserHandler(c *gin.Context) {}
func deleteUserHandler(c *gin.Context) {}
func getAllProductsHandler(c *gin.Context) {}
func getProductHandler(c *gin.Context) {}
func createProductHandler(c *gin.Context) {}
func updateProductHandler(c *gin.Context) {}
func deleteProductHandler(c *gin.Context) {}
func authMiddleware() gin.HandlerFunc { return func(c *gin.Context) { c.Next() } }
func adminMiddleware() gin.HandlerFunc { return func(c *gin.Context) { c.Next() } }
Summary
Gin route groups are a powerful feature that helps you organize your API endpoints efficiently. They allow you to:
- Group related routes under a common path prefix
- Apply middleware selectively to specific groups of routes
- Nest route groups to create hierarchical API structures
- Separate concerns and improve code organization
By using route groups effectively, you can build clean, maintainable, and scalable web applications with Gin.
Exercises
To solidify your understanding of Gin route groups, try these exercises:
-
Create a simple API with public and protected route groups for a library management system (books, authors, users).
-
Implement nested route groups for an e-commerce API with products, categories, users, and orders endpoints.
-
Build an API with versioning using route groups (v1, v2) and implement different behavior in each version.
-
Create a modular API structure where routes are defined in separate files and imported into the main application.
Additional Resources
- Gin Framework Official Documentation
- Gin GitHub Repository
- RESTful API Design Best Practices
- HTTP Status Codes
Happy coding with Gin route groups!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)