Skip to main content

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:

  1. Group related routes under a common path prefix
  2. Apply middleware to specific groups of routes
  3. Create nested groups for more complex route hierarchies
  4. 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:

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

  1. We create a new route group with e.Group("/api")
  2. All routes defined on this group will have the /api prefix
  3. 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:

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

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

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

  1. Global middleware (Logger)
  2. First API group middleware
  3. Additional API middleware
  4. Route handler

Real-World Application Example

Let's create a more comprehensive example that represents a typical web application with different sections:

go
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

  1. Group by function: Create groups for routes that serve similar purposes (API, admin, auth, etc.)
  2. Group by authorization level: Separate public routes from those requiring authentication
  3. Use versioning in APIs: Create nested groups for different API versions
  4. Keep middleware relevant: Apply middleware only to the groups that need it
  5. 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:

  1. Create a simple API with v1 and v2 versions using nested route groups
  2. Implement a blog application with public routes and an admin section protected by authentication
  3. Add rate limiting middleware to specific route groups while leaving others unrestricted
  4. Create a complex nested group structure (at least 3 levels deep) and trace the full paths
  5. Implement different logging middleware for different route groups to see how they behave

Additional Resources

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