Fiber middleware executes before your route handlers, while route groups allow you to organize routes under a common prefix and share middleware across them. You apply middleware globally, to specific groups, or to individual routes by passing functions to the Use, Group, or route definition methods.
Here is a practical example showing how to chain middleware and organize routes:
package main
import (
"log"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/logger"
"github.com/gofiber/fiber/v2/middleware/recover"
)
func main() {
app := fiber.New()
// 1. Global Middleware: Runs for every request
app.Use(logger.New())
app.Use(recover.New())
// 2. Route Group with Middleware: Applies to all routes in this group
api := app.Group("/api", func(c *fiber.Ctx) error {
// Custom middleware logic (e.g., Auth check)
log.Println("API Middleware executed at", time.Now())
return c.Next()
})
// Routes inside the group inherit the "/api" prefix and the middleware
api.Get("/users", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"message": "List of users"})
})
api.Post("/users", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"message": "User created"})
})
// 3. Nested Group: Further organization with additional middleware
auth := api.Group("/auth", func(c *fiber.Ctx) error {
// Additional middleware specific to auth routes
log.Println("Auth Middleware executed")
return c.Next()
})
auth.Post("/login", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"message": "Login successful"})
})
// 4. Individual Route Middleware: Applies only to this specific handler
app.Get("/health", func(c *fiber.Ctx) error {
return c.SendString("OK")
}, func(c *fiber.Ctx) error {
// Specific logic for health check
log.Println("Health check hit")
return c.Next()
})
log.Fatal(app.Listen(":3000"))
}
In this setup, a request to /api/users triggers the global logger, the global recover middleware, the group middleware for /api, and finally the handler. A request to /api/auth/login adds the nested group middleware to that chain. You can nest groups infinitely to create complex permission hierarchies, and you can pass multiple middleware functions to a single route or group by listing them sequentially.
For quick testing without a full server, you can also use the Test method to verify middleware behavior:
import (
"net/http/httptest"
"testing"
)
func TestMiddleware(t *testing.T) {
req := httptest.NewRequest("GET", "/api/users", nil)
resp, err := app.Test(req, -1)
if err != nil || resp.StatusCode != 200 {
t.Fatalf("Expected 200, got %d", resp.StatusCode)
}
}
This approach keeps your code DRY by avoiding repeated middleware registration and makes your API structure immediately readable.