How to Use Fiber Middleware and Route Groups

Web
Fiber middleware executes before your route handlers, while route groups allow you to organize routes under a common prefix and share middleware across them.

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.