Skip to main content

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:

  1. Group multiple routes under a common URL prefix
  2. Apply middleware to specific sets of routes
  3. Organize your API endpoints logically
  4. 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:

go
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 users
  • GET /v1/users/:id - Returns a specific user by ID
  • POST /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:

go
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:

bash
curl http://localhost:8080/public/posts

For protected endpoint (will fail without token):

bash
curl http://localhost:8080/admin/users

For protected endpoint with token:

bash
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:

go
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:

go
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:

  1. Global middleware (Logger) applied to all routes
  2. Different middleware stacks for different route groups:
    • Public routes: only logging
    • User routes: logging + authentication
    • Admin routes: logging + authentication + admin role check
  3. RESTful organization of endpoints

Best Practices for Route Groups

When working with Gin route groups, consider these best practices:

  1. Logical organization: Group routes based on resource types, authentication requirements, or API versions.

  2. Middleware chaining: Apply middleware in order from least to most specific.

  3. Separation of concerns: Consider moving route definitions to separate files when your application grows.

  4. Consistent naming: Use consistent naming conventions for your routes and endpoints.

  5. Documentation: Comment your route groups to make the structure clear for other developers.

Here's a modular approach example:

go
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:

  1. Create a simple API with public and protected route groups for a library management system (books, authors, users).

  2. Implement nested route groups for an e-commerce API with products, categories, users, and orders endpoints.

  3. Build an API with versioning using route groups (v1, v2) and implement different behavior in each version.

  4. Create a modular API structure where routes are defined in separate files and imported into the main application.

Additional Resources

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! :)