Echo Route Groups
Introduction
As your web application grows in complexity, managing routes becomes increasingly challenging. Echo's Route Groups provide an elegant solution to organize related routes, apply common middleware to sets of routes, and maintain a clean, structured codebase. This feature is particularly useful for creating API versions, admin sections, or any logically grouped endpoints.
In this guide, we'll explore how to use Echo Route Groups to enhance your application's structure and maintainability.
What Are Route Groups?
Route Groups in Echo allow you to:
- Group related routes under a common path prefix
- Apply middleware to specific groups of routes
- Create nested groups for more complex route hierarchies
- Keep your routing code organized and maintainable
Think of route groups as folders in a file system—they help you categorize and organize your endpoints logically.
Basic Route Group Usage
Let's start with a simple example of how to create a route group:
package main
import (
"net/http"
"github.com/labstack/echo/v4"
)
func main() {
e := echo.New()
// Create a route group with prefix "/api"
api := e.Group("/api")
// Define routes within the group
api.GET("/users", getUsers)
api.POST("/users", createUser)
api.GET("/users/:id", getUser)
e.Start(":8080")
}
func getUsers(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{"message": "Getting all users"})
}
func createUser(c echo.Context) error {
return c.JSON(http.StatusCreated, map[string]string{"message": "User created"})
}
func getUser(c echo.Context) error {
id := c.Param("id")
return c.JSON(http.StatusOK, map[string]string{"message": "Getting user " + id})
}
How this works:
- We create a new route group with
e.Group("/api")
- All routes defined on this group will have the
/api
prefix - For example,
api.GET("/users", getUsers)
will respond to/api/users
Applying Middleware to Groups
One of the most powerful features of route groups is the ability to apply middleware to all routes within a group:
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
// Create an admin group with specific middleware
admin := e.Group("/admin", middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) {
if username == "admin" && password == "secret" {
return true, nil
}
return false, nil
}))
// All these routes require authentication
admin.GET("/dashboard", adminDashboard)
admin.GET("/stats", adminStats)
admin.POST("/users", adminCreateUser)
// Public routes that don't need authentication
e.GET("/", homePage)
e.GET("/about", aboutPage)
e.Start(":8080")
}
func adminDashboard(c echo.Context) error {
return c.String(http.StatusOK, "Admin Dashboard")
}
func adminStats(c echo.Context) error {
return c.String(http.StatusOK, "Admin Statistics")
}
func adminCreateUser(c echo.Context) error {
return c.String(http.StatusOK, "Create User from Admin")
}
func homePage(c echo.Context) error {
return c.String(http.StatusOK, "Home Page")
}
func aboutPage(c echo.Context) error {
return c.String(http.StatusOK, "About Page")
}
In this example, we've applied Basic Authentication middleware only to routes under the /admin
group. The public routes remain accessible without authentication.
Nested Route Groups
For more complex applications, you can create nested route groups for additional organization:
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
// API route group
api := e.Group("/api")
// API v1 group (nested under /api)
v1 := api.Group("/v1")
v1.GET("/users", getV1Users)
v1.GET("/products", getV1Products)
// API v2 group (nested under /api)
v2 := api.Group("/v2")
v2.GET("/users", getV2Users)
v2.GET("/products", getV2Products)
e.Start(":8080")
}
func getV1Users(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{"version": "1", "entity": "users"})
}
func getV1Products(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{"version": "1", "entity": "products"})
}
func getV2Users(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{"version": "2", "entity": "users"})
}
func getV2Products(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{"version": "2", "entity": "products"})
}
This creates the following routes:
/api/v1/users
/api/v1/products
/api/v2/users
/api/v2/products
Nested groups are perfect for API versioning or when you need multiple layers of organization.
Middleware Application Order
When applying middleware to route groups, be aware of the execution order:
package main
import (
"fmt"
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
// Global middleware
e.Use(middleware.Logger())
// Group with middleware
api := e.Group("/api", func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
fmt.Println("API group middleware executed")
return next(c)
}
})
// Additional middleware on the group
api.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
fmt.Println("Additional API middleware executed")
return next(c)
}
})
// Route handler
api.GET("/test", func(c echo.Context) error {
return c.String(http.StatusOK, "Test endpoint")
})
e.Start(":8080")
}
The execution order will be:
- Global middleware (Logger)
- First API group middleware
- Additional API middleware
- Route handler
Real-World Application Example
Let's create a more comprehensive example that represents a typical web application with different sections:
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
// Sample middleware functions
func loggerMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
println("Request:", c.Request().Method, c.Request().URL.Path)
return next(c)
}
}
func authMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// In a real application, check token, session, etc.
token := c.Request().Header.Get("Authorization")
if token == "" {
return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Unauthorized"})
}
return next(c)
}
}
func main() {
e := echo.New()
// Global middleware
e.Use(middleware.Recover())
e.Use(middleware.CORS())
// Public routes
e.GET("/", func(c echo.Context) error {
return c.String(http.StatusOK, "Welcome to our service!")
})
// API routes - grouped with version
api := e.Group("/api", loggerMiddleware)
// V1 API - further grouped
v1 := api.Group("/v1")
// Auth required routes
auth := v1.Group("/auth", authMiddleware)
auth.GET("/profile", getProfile)
auth.PUT("/profile", updateProfile)
// Public API routes
v1.GET("/products", getProducts)
v1.GET("/products/:id", getProduct)
// Admin section - with basic auth
admin := e.Group("/admin", middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) {
// In real app, check against database
return username == "admin" && password == "admin123", nil
}))
admin.GET("/dashboard", adminDashboard)
admin.GET("/users", adminListUsers)
e.Start(":8080")
}
// Handler functions
func getProfile(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"name": "John Doe",
"email": "[email protected]",
})
}
func updateProfile(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"status": "profile updated",
})
}
func getProducts(c echo.Context) error {
products := []map[string]interface{}{
{"id": 1, "name": "Product 1", "price": 29.99},
{"id": 2, "name": "Product 2", "price": 39.99},
}
return c.JSON(http.StatusOK, products)
}
func getProduct(c echo.Context) error {
id := c.Param("id")
return c.JSON(http.StatusOK, map[string]interface{}{
"id": id,
"name": "Product " + id,
"price": 29.99,
})
}
func adminDashboard(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"stats": "Sales are up 15% this month",
})
}
func adminListUsers(c echo.Context) error {
users := []map[string]string{
{"id": "1", "name": "User 1", "email": "[email protected]"},
{"id": "2", "name": "User 2", "email": "[email protected]"},
}
return c.JSON(http.StatusOK, users)
}
This example demonstrates:
- Global middleware for all routes
- Route groups with prefixes for different sections (
/api
,/admin
) - Nested groups for API versioning (
/api/v1
) - Different authentication mechanisms for different sections
- Logical organization of related endpoints
Best Practices for Route Groups
- Group by function: Create groups for routes that serve similar purposes (API, admin, auth, etc.)
- Group by authorization level: Separate public routes from those requiring authentication
- Use versioning in APIs: Create nested groups for different API versions
- Keep middleware relevant: Apply middleware only to the groups that need it
- Use descriptive variable names: Name your groups meaningfully (e.g.,
admin
,apiV1
,public
)
Summary
Echo Route Groups are a powerful feature that help you organize your web application's routes in a logical and maintainable way. They allow you to:
- Group related routes under common prefixes
- Apply middleware to specific sets of routes
- Create nested hierarchies for complex applications
- Separate public and protected routes
- Version your APIs cleanly
By effectively using route groups, you can keep your Echo application code clean, maintainable, and well-structured as it grows in complexity.
Exercises
To practice using Echo Route Groups, try these exercises:
- Create a simple API with v1 and v2 versions using nested route groups
- Implement a blog application with public routes and an admin section protected by authentication
- Add rate limiting middleware to specific route groups while leaving others unrestricted
- Create a complex nested group structure (at least 3 levels deep) and trace the full paths
- Implement different logging middleware for different route groups to see how they behave
Additional Resources
- Echo Framework Official Documentation
- Middleware in Echo
- RESTful API Design Best Practices
- API Versioning Strategies
With Echo's route groups, you can build complex, well-organized web applications that remain maintainable as they grow. Start small and progressively organize your routes as your application's needs evolve.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)