Skip to main content
Rewrite middleware for Fiber rewrites the URL path based on provided rules. Unlike redirect middleware, rewrite modifies the request path internally without sending a redirect response to the client.

Signatures

func New(config ...Config) fiber.Handler

Usage

import (
    "github.com/gofiber/fiber/v3"
    "github.com/gofiber/fiber/v3/middleware/rewrite"
)

Basic Usage

app.Use(rewrite.New(rewrite.Config{
    Rules: map[string]string{
        "/old": "/new",
    },
}))

Pattern Matching with Wildcards

app.Use(rewrite.New(rewrite.Config{
    Rules: map[string]string{
        "/old":              "/new",
        "/api/*":            "/$1",
        "/js/*":             "/public/javascript/$1",
        "/users/*/orders/*": "/user/$1/order/$2",
    },
}))

API Versioning

app.Use(rewrite.New(rewrite.Config{
    Rules: map[string]string{
        "/api/v1/*": "/v1/$1",
        "/api/v2/*": "/v2/$1",
    },
}))

// Requests to /api/v1/users internally route to /v1/users
app.Get("/v1/:resource", v1Handler)
app.Get("/v2/:resource", v2Handler)

Skip Specific Paths

app.Use(rewrite.New(rewrite.Config{
    Next: func(c fiber.Ctx) bool {
        // Skip rewriting for admin routes
        return strings.HasPrefix(c.Path(), "/admin")
    },
    Rules: map[string]string{
        "/old/*": "/new/$1",
    },
}))

Configuration

Next
func(fiber.Ctx) bool
default:"nil"
Defines a function to skip this middleware when it returns true.
Rules
map[string]string
required
Defines the URL path rewrite rules. The values captured in asterisk wildcards can be retrieved by index e.g. $1, $2 and so on.Example rules:
  • "/old": "/new" - Simple rewrite
  • "/api/*": "/$1" - Capture and reuse path
  • "/js/*": "/public/javascript/$1" - Add prefix
  • "/users/*/orders/*": "/user/$1/order/$2" - Multiple captures

Default Config

The rewrite middleware does not have default configuration values. Rules must be explicitly provided.

Common Use Cases

Clean URLs

app.Use(rewrite.New(rewrite.Config{
    Rules: map[string]string{
        "/products/:id": "/p/:id",
        "/categories/:name": "/c/:name",
    },
}))

// Users see: /products/123
// Internally routes to: /p/123
app.Get("/p/:id", productHandler)

Static File Organization

app.Use(rewrite.New(rewrite.Config{
    Rules: map[string]string{
        "/static/js/*":  "/assets/javascript/$1",
        "/static/css/*": "/assets/stylesheets/$1",
        "/static/img/*": "/assets/images/$1",
    },
}))

app.Static("/assets", "./public")

Multi-Tenancy

app.Use(rewrite.New(rewrite.Config{
    Rules: map[string]string{
        "/:tenant/api/*": "/api/$1",
    },
}))

app.Use(func(c fiber.Ctx) error {
    tenant := c.Params("tenant")
    c.Locals("tenant", tenant)
    return c.Next()
})

app.Get("/api/users", func(c fiber.Ctx) error {
    tenant := c.Locals("tenant").(string)
    // Handle request for specific tenant
    return c.JSON(users)
})

Backward Compatibility

app.Use(rewrite.New(rewrite.Config{
    Rules: map[string]string{
        "/legacy/user/*":    "/api/v2/users/$1",
        "/legacy/product/*": "/api/v2/products/$1",
    },
}))

// Old URLs still work, internally using new routes
app.Get("/api/v2/users/:id", userHandler)

Language Routing

app.Use(rewrite.New(rewrite.Config{
    Rules: map[string]string{
        "/en/*": "/$1",
        "/es/*": "/$1",
        "/fr/*": "/$1",
    },
}))

app.Use(func(c fiber.Ctx) error {
    // Extract language from original path
    path := c.OriginalURL()
    if strings.HasPrefix(path, "/en") {
        c.Locals("lang", "en")
    } else if strings.HasPrefix(path, "/es") {
        c.Locals("lang", "es")
    }
    return c.Next()
})

Microservice Routing

app.Use(rewrite.New(rewrite.Config{
    Rules: map[string]string{
        "/api/users/*":    "/users-service/$1",
        "/api/products/*": "/products-service/$1",
        "/api/orders/*":   "/orders-service/$1",
    },
}))

// Route to different service handlers
app.All("/users-service/*", proxyToUsersService)
app.All("/products-service/*", proxyToProductsService)

Best Practices

Order Matters

// More specific rules should be defined first
app.Use(rewrite.New(rewrite.Config{
    Rules: map[string]string{
        "/api/v2/admin/*": "/admin/v2/$1",  // Specific
        "/api/v2/*":       "/v2/$1",        // General
    },
}))

Use with Router Groups

app.Use(rewrite.New(rewrite.Config{
    Rules: map[string]string{
        "/api/*": "/$1",
    },
}))

api := app.Group("/")
api.Get("/users", getUsers)      // Accessed via /api/users
api.Post("/users", createUser)   // Accessed via /api/users

Combine with Other Middleware

// Rewrite must come before route registration
app.Use(rewrite.New(rewrite.Config{
    Rules: map[string]string{
        "/v1/*": "/api/v1/$1",
    },
}))

// Other middleware after rewrite
app.Use(logger.New())
app.Use(cors.New())

Testing Rewrite Rules

app.Use(rewrite.New(rewrite.Config{
    Rules: map[string]string{
        "/test/*": "/actual/$1",
    },
}))

app.Get("/actual/:path", func(c fiber.Ctx) error {
    return c.SendString("Path: " + c.Params("path"))
})

// Test: GET /test/hello -> "Path: hello"

Notes

  • Rewrite happens internally - the client is not aware of the URL change
  • Wildcards (*) in rules capture path segments that can be referenced as $1, $2, etc.
  • Rewrite modifies c.Path() but c.OriginalURL() remains unchanged
  • The middleware uses regular expressions internally for pattern matching
  • Query parameters and fragments are preserved during rewriting
  • Use rewrite for internal routing logic; use redirect for external URL changes

Build docs developers (and LLMs) love