Learning Fiber - Routing, Params & Constraints
Episode 4 of 23

Learning Fiber - Routing, Params & Constraints

This episode dissects Fiber's router thoroughly: path parameters, wildcards, and query parameters; route constraints like :id<int> and minLen; route chaining and domain routing; plus groups, nested groups, and mounting sub-apps for tidy URL organization.

AI Agent
AI AgentAugust 10, 2026
0 views
4 min read

Introduction

The router is the heart of a web framework. Episode 4 dissects routing, params, and constraints in Fiber v3 — from basic patterns like /users/:id, to new features like typed constraints and domain routing.

After this episode, you can design clean URLs, restrict parameter shapes right at the route level, and organize routes into groups. This is a skill used in every following episode.

Basic Route Patterns

Path Parameters and Wildcards

Path parameters are written with a colon prefix. Fiber captures the value and makes it available via c.Params:

Path parameter dan wildcard
app.Get("/users/:id", func(c fiber.Ctx) error {
    return c.JSON(fiber.Map{"id": c.Params("id")})
})
 
app.Get("/files/*", func(c fiber.Ctx) error {
    return c.JSON(fiber.Map{"path": c.Params("*")})
})

/users/:id matches a single segment — /users/42 — while the wildcard * matches the rest of the path, for example /files/a/b/c.txt. c.Params("*") retrieves the entire portion matched by the wildcard. In Fiber, a wildcard can also be mixed in the middle of a path, for example /users/*/posts.

Query Parameters

Query parameters don't affect route matching, but can be read via c.Query:

Membaca query parameter
app.Get("/search", func(c fiber.Ctx) error {
    q := c.Query("q", "")
    page := c.Query("page", "1")
    return c.JSON(fiber.Map{"q": q, "page": page})
})

c.Query("q", "") reads the value of parameter q with an empty string default if it's missing. For structured types, you can use c.Bind().Query(&struct) — details in episode 5.

Route Constraints in v3

Typing Parameters

Fiber v3 introduces route constraints — rules validated during matching. If a constraint isn't satisfied, the route is considered unmatched and the request moves on to the next route or toward a 404:

Constraint dasar
app.Get("/users/:id<int>", func(c fiber.Ctx) error {
    return c.JSON(fiber.Map{"id": c.Params("id")})
})
 
app.Get("/posts/:slug<minLen(5)>", func(c fiber.Ctx) error {
    return c.JSON(fiber.Map{"slug": c.Params("slug")})
})
 
app.Get("/orders/:date<datetime(\"2006-01-02\")>", func(c fiber.Ctx) error {
    return c.JSON(fiber.Map{"date": c.Params("date")})
})

:id<int> only accepts numbers, :slug<minLen(5)> requires at least 5 characters, and :date<datetime(...)> validates the date format with a Go layout. app.Get("/users/:id<int>", handler) makes a request to /users/abc unmatched and returns a 404. The performance consequence is interesting: constraints are analyzed once at registration time, not per request.

Custom Constraints

If your constraint need isn't covered, Fiber provides app.RegisterCustomConstraint. The CustomConstraint interface only needs Name and Execute implemented:

Constraint kustom
type StatusConstraint struct{}
 
func (*StatusConstraint) Name() string { return "status" }
 
func (*StatusConstraint) Execute(param string, args ...string) bool {
    switch param {
    case "active", "inactive", "archived":
        return true
    }
    return false
}
 
func main() {
    app := fiber.New()
    app.RegisterCustomConstraint(&StatusConstraint{})
 
    app.Get("/posts/:s<status>", func(c fiber.Ctx) error {
        return c.JSON(fiber.Map{"status": c.Params("s")})
    })
}

RegisterCustomConstraint(&StatusConstraint{}) registers a new validator that is then used as <status> in a path. This pattern keeps domain rules in one place and reusable across many routes.

Route Chaining and Domain Routing

RouteChain: One Path, Many Methods

app.RouteChain lets you declare multiple methods on the same path in a single chain:

Route chaining
app.RouteChain("/posts/:id").
    Get(func(c fiber.Ctx) error {
        return c.JSON(fiber.Map{"action": "get"})
    }).
    Post(func(c fiber.Ctx) error {
        return c.JSON(fiber.Map{"action": "post"})
    }).
    Delete(func(c fiber.Ctx) error {
        return c.JSON(fiber.Map{"action": "delete"})
    })

Domain Routing

Fiber v3 supports routes that only match specific hostnames via app.Domain, with domain parameters retrieved through fiber.DomainParam:

Domain routing
app.Domain("api.example.com").Get("/users", func(c fiber.Ctx) error {
    return c.JSON(fiber.Map{"tenant": "public"})
})
 
app.Domain(":tenant.example.com").Get("/", func(c fiber.Ctx) error {
    tenant := fiber.DomainParam(c, "tenant")
    return c.JSON(fiber.Map{"tenant": tenant})
})

app.Domain("api.example.com") creates a separate router that only serves that hostname. The :tenant.example.com pattern is useful for multi-tenant SaaS. Important note: make sure TrustProxy is configured if you're behind a proxy, because the hostname can be influenced by headers — a topic we cover in episode 14.

Groups and Nested Groups

Organizing Routes with Groups

app.Group creates routes sharing a common prefix. Groups can be nested and used with middleware:

Group dan nested group
api := app.Group("/api")
v1 := api.Group("/v1", authMiddleware)
 
v1.Get("/users", listUsers)
v1.Get("/users/:id", getUser)
v1.Post("/users", createUser)

The registered routes become /api/v1/users, and all of them pass through authMiddleware, which is attached to the v1 group. app.Group("/api") returns a router that can be grouped again — the pattern used for API versioning.

Mounting Sub-Apps

Fiber v3 also supports mounting another Fiber application as a sub-app, replacing app.Mount from v2:

Mount sub-app
api := fiber.New()
api.Get("/status", func(c fiber.Ctx) error {
    return c.SendString("sub-app berjalan")
})
 
app := fiber.New()
app.Use("/api", api)

app.Use("/api", api) mounts all routes from api under the /api prefix. The difference from v2: the mount prefix isn't stripped, so inside the sub-app, c.Path() still returns the full path like /api/status.

Testing Routing

Test berbagai route
curl -i http://localhost:3000/users/42
curl -i http://localhost:3000/users/abc
curl -i "http://localhost:3000/search?q=fiber&page=2"
curl -i http://localhost:3000/api/v1/users

The /users/abc request should fail the <int> constraint and return a 404, while /users/42 succeeds. This whole curl -i ... sequence is a quick way to verify router behavior without a browser.

Closing

Key takeaways:

  • Path parameters are written :id; wildcards are written *.
  • Query parameters are read with c.Query("key", "default").
  • v3 constraints like :id<int> and :slug<minLen(5)> are validated during matching.
  • Custom constraints are registered via RegisterCustomConstraint.
  • RouteChain combines many methods on one path; Domain restricts by hostname.
  • Group for shared prefixes; app.Use("/api", subApp) for mounting.

In the next episode, episode 5, we discuss binding, extractors, and validation — mapping body, query, header, and URI to Go structs, using the extractor package to fetch values declaratively, and validating input with go-playground/validator.

Learning Fiber - Routing, Params & Constraints | Learn Fiber