Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -905,15 +905,35 @@ func (app *App) Group(prefix string, handlers ...Handler) Router {
return grp
}

// Route is used to define routes with a common prefix inside the common function.
// Uses Group method to define new sub-router.
func (app *App) Route(path string) Register {
// RouteChain creates a Registering instance that lets you declare a stack of
// handlers for the same route. Handlers defined via the returned Register are
// scoped to the provided path.
func (app *App) RouteChain(path string) Register {
// Create new route
route := &Registering{app: app, path: path}

return route
}

// Route is used to define routes with a common prefix inside the supplied
// function. It mirrors the legacy helper and reuses the Group method to create
// a sub-router.
func (app *App) Route(prefix string, fn func(router Router), name ...string) Router {
if fn == nil {
panic("route handler 'fn' cannot be nil")
}
// Create new group
group := app.Group(prefix)
if len(name) > 0 {
group.Name(name[0])
}

// Define routes
fn(group)

return group
}

// Error makes it compatible with the `error` interface.
func (e *Error) Error() string {
return e.Message
Expand Down
31 changes: 27 additions & 4 deletions app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1360,13 +1360,13 @@ func Test_App_Group(t *testing.T) {
require.Equal(t, 200, resp.StatusCode, "Status code")
}

func Test_App_Route(t *testing.T) {
func Test_App_RouteChain(t *testing.T) {
t.Parallel()
dummyHandler := testEmptyHandler

app := New()

register := app.Route("/test").
register := app.RouteChain("/test").
Get(dummyHandler).
Head(dummyHandler).
Post(dummyHandler).
Expand All @@ -1387,7 +1387,7 @@ func Test_App_Route(t *testing.T) {
testStatus200(t, app, "/test", MethodTrace)
testStatus200(t, app, "/test", MethodPatch)

register.Route("/v1").Get(dummyHandler).Post(dummyHandler)
register.RouteChain("/v1").Get(dummyHandler).Post(dummyHandler)

resp, err := app.Test(httptest.NewRequest(MethodPost, "/test/v1", nil))
require.NoError(t, err, "app.Test(req)")
Expand All @@ -1397,7 +1397,7 @@ func Test_App_Route(t *testing.T) {
require.NoError(t, err, "app.Test(req)")
require.Equal(t, 200, resp.StatusCode, "Status code")

register.Route("/v1").Route("/v2").Route("/v3").Get(dummyHandler).Trace(dummyHandler)
register.RouteChain("/v1").RouteChain("/v2").RouteChain("/v3").Get(dummyHandler).Trace(dummyHandler)

resp, err = app.Test(httptest.NewRequest(MethodTrace, "/test/v1/v2/v3", nil))
require.NoError(t, err, "app.Test(req)")
Expand All @@ -1408,6 +1408,29 @@ func Test_App_Route(t *testing.T) {
require.Equal(t, 200, resp.StatusCode, "Status code")
}

func Test_App_Route(t *testing.T) {
t.Parallel()

app := New()

app.Route("/test", func(api Router) {
api.Get("/foo", testEmptyHandler).Name("foo")

api.Route("/bar", func(bar Router) {
bar.Get("/", testEmptyHandler).Name("index")
}, "bar.")
}, "test.")

testStatus200(t, app, "/test/foo", MethodGet)

resp, err := app.Test(httptest.NewRequest(MethodGet, "/test/bar/", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, http.StatusOK, resp.StatusCode, "Status code")

require.Equal(t, "/test/foo", app.GetRoute("test.foo").Path)
require.Equal(t, "/test/bar/", app.GetRoute("test.bar.index").Path)
}

func Test_App_Deep_Group(t *testing.T) {
t.Parallel()
runThroughCount := 0
Expand Down
35 changes: 25 additions & 10 deletions docs/api/app.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,14 +138,14 @@ func handler(c fiber.Ctx) error {
}
```

### Route
### RouteChain

Returns an instance of a single route, which you can then use to handle HTTP verbs with optional middleware.

Similar to [`Express`](https://expressjs.com/de/api.html#app.route).
Similar to [`Express`](https://expressjs.com/en/api.html#app.route).

```go title="Signature"
func (app *App) Route(path string) Register
func (app *App) RouteChain(path string) Register
```

<details>
Expand All @@ -166,7 +166,7 @@ type Register interface {

Add(methods []string, handler Handler, handlers ...Handler) Register

Route(path string) Register
RouteChain(path string) Register
}
```

Expand All @@ -184,12 +184,12 @@ import (
func main() {
app := fiber.New()

// Use `Route` as a chainable route declaration method
app.Route("/test").Get(func(c fiber.Ctx) error {
// Use `RouteChain` as a chainable route declaration method
app.RouteChain("/test").Get(func(c fiber.Ctx) error {
return c.SendString("GET /test")
})

app.Route("/events").All(func(c fiber.Ctx) error {
app.RouteChain("/events").All(func(c fiber.Ctx) error {
// Runs for all HTTP verbs first
// Think of it as route-specific middleware!
}).
Expand All @@ -202,12 +202,12 @@ func main() {
})

// Combine multiple routes
app.Route("/v2").Route("/user").Get(func(c fiber.Ctx) error {
return c.SendString("GET /v2/user")
app.RouteChain("/reports").RouteChain("/daily").Get(func(c fiber.Ctx) error {
return c.SendString("GET /reports/daily")
})

// Use multiple methods
app.Route("/api").Get(func(c fiber.Ctx) error {
app.RouteChain("/api").Get(func(c fiber.Ctx) error {
return c.SendString("GET /api")
}).Post(func(c fiber.Ctx) error {
return c.SendString("POST /api")
Expand All @@ -217,6 +217,21 @@ func main() {
}
```

### Route

Defines routes with a common prefix inside the supplied function. Internally it uses [`Group`](#group) to create a sub-router and accepts an optional name prefix.

```go title="Signature"
func (app *App) Route(prefix string, fn func(router Router), name ...string) Router
```

```go title="Example"
app.Route("/test", func(api fiber.Router) {
api.Get("/foo", handler).Name("foo") // /test/foo (name: test.foo)
api.Get("/bar", handler).Name("bar") // /test/bar (name: test.bar)
}, "test.")
```

### HandlersCount

Returns the number of registered handlers.
Expand Down
16 changes: 8 additions & 8 deletions docs/whats_new.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,18 +328,18 @@ In `v2` one handler was already mandatory when the route has been registered, bu

### Route chaining

The route method is now like [`Express`](https://expressjs.com/de/api.html#app.route) which gives you the option of a different notation and allows you to concatenate the route declaration.
Fiber v3 introduces a dedicated `RouteChain` helper, inspired by [`Express`](https://expressjs.com/en/api.html#app.route), for declaring a stack of handlers on the same path. The original `Route` helper for prefix encapsulation also remains available.

```diff
- Route(prefix string, fn func(router Router), name ...string) Router
+ Route(path string) Register
- Route(path string) Register
+ RouteChain(path string) Register
```

<details>
<summary>Example</summary>

```go
app.Route("/api").Route("/user/:id?")
app.RouteChain("/api").RouteChain("/user/:id?")
.Get(func(c fiber.Ctx) error {
// Get user
return c.JSON(fiber.Map{"message": "Get user", "id": c.Params("id")})
Expand All @@ -360,11 +360,11 @@ app.Route("/api").Route("/user/:id?")

</details>

You can find more information about `app.Route` in the [API documentation](./api/app#route).
You can find more information about `app.RouteChain` and `app.Route` in the API documentation ([RouteChain](./api/app#routechain), [Route](./api/app#route)).

### Middleware registration

We have aligned our method for middlewares closer to [`Express`](https://expressjs.com/de/api.html#app.use) and now also support the [`Use`](./api/app#use) of multiple prefixes.
We have aligned our method for middlewares closer to [`Express`](https://expressjs.com/en/api.html#app.use) and now also support the [`Use`](./api/app#use) of multiple prefixes.

Prefix matching is now stricter: partial matches must end at a slash boundary (or be an exact match). This keeps `/api` middleware from running on `/apiv1` while still allowing `/api/:version` style patterns that leverage route parameters, optional segments, or wildcards.

Expand Down Expand Up @@ -1655,7 +1655,7 @@ app.Use("/api", apiApp)

#### Route Chaining

Refer to the [route chaining](#route-chaining) section for details on migrating `Route`.
Refer to the [route chaining](#route-chaining) section for details on the new `RouteChain` helper. The `Route` function now matches its v2 behavior for prefix encapsulation.

```go
// Before
Expand All @@ -1675,7 +1675,7 @@ app.Route("/api", func(apiGrp Router) {

```go
// After
app.Route("/api").Route("/user/:id?")
app.RouteChain("/api").RouteChain("/user/:id?")
.Get(func(c fiber.Ctx) error {
// Get user
return c.JSON(fiber.Map{"message": "Get user", "id": c.Params("id")})
Expand Down
27 changes: 23 additions & 4 deletions group.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,11 +196,30 @@ func (grp *Group) Group(prefix string, handlers ...Handler) Router {
return newGrp
}

// Route is used to define routes with a common prefix inside the common function.
// Uses Group method to define new sub-router.
func (grp *Group) Route(path string) Register {
// RouteChain creates a Registering instance scoped to the group's prefix,
// allowing chained route declarations for the same path.
func (grp *Group) RouteChain(path string) Register {
// Create new group
register := &Registering{app: grp.app, path: getGroupPath(grp.Prefix, path)}
register := &Registering{app: grp.app, group: grp, path: getGroupPath(grp.Prefix, path)}

return register
}

// Route is used to define routes with a common prefix inside the supplied
// function. It mirrors the legacy helper and reuses the Group method to create
// a sub-router.
func (grp *Group) Route(prefix string, fn func(router Router), name ...string) Router {
if fn == nil {
panic("route handler 'fn' cannot be nil")
}
// Create new group
group := grp.Group(prefix)
if len(name) > 0 {
group.Name(name[0])
}

// Define routes
fn(group)

return group
}
4 changes: 2 additions & 2 deletions middleware/cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -875,7 +875,7 @@ func Test_Cache_WithHead(t *testing.T) {
return c.SendString(strconv.Itoa(count))
}

app.Route("/").Get(handler).Head(handler)
app.RouteChain("/").Get(handler).Head(handler)

req := httptest.NewRequest(fiber.MethodHead, "/", nil)
resp, err := app.Test(req)
Expand Down Expand Up @@ -904,7 +904,7 @@ func Test_Cache_WithHeadThenGet(t *testing.T) {
handler := func(c fiber.Ctx) error {
return c.SendString(fiber.Query[string](c, "cache"))
}
app.Route("/").Get(handler).Head(handler)
app.RouteChain("/").Get(handler).Head(handler)

headResp, err := app.Test(httptest.NewRequest(fiber.MethodHead, "/?cache=123", nil))
require.NoError(t, err)
Expand Down
26 changes: 13 additions & 13 deletions register.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

package fiber

// Register defines all router handle interface generate by Route().
// Register defines all router handle interface generate by RouteChain().
type Register interface {
All(handler Handler, handlers ...Handler) Register
Get(handler Handler, handlers ...Handler) Register
Expand All @@ -19,43 +19,43 @@ type Register interface {

Add(methods []string, handler Handler, handlers ...Handler) Register

Route(path string) Register
RouteChain(path string) Register
}

var _ (Register) = (*Registering)(nil)

// Registering provides route registration helpers for a specific path on the
// application instance.
type Registering struct {
app *App
app *App
group *Group

path string
}

// All registers a middleware route that will match requests
// with the provided path which is stored in register struct.
//
// app.Route("/").All(func(c fiber.Ctx) error {
// app.RouteChain("/").All(func(c fiber.Ctx) error {
// return c.Next()
// })
// app.Route("/api").All(func(c fiber.Ctx) error {
// app.RouteChain("/api").All(func(c fiber.Ctx) error {
// return c.Next()
// })
// app.Route("/api").All(handler, func(c fiber.Ctx) error {
// app.RouteChain("/api").All(handler, func(c fiber.Ctx) error {
// return c.Next()
// })
//
// This method will match all HTTP verbs: GET, POST, PUT, HEAD etc...
func (r *Registering) All(handler Handler, handlers ...Handler) Register {
r.app.register([]string{methodUse}, r.path, nil, append([]Handler{handler}, handlers...)...)
r.app.register([]string{methodUse}, r.path, r.group, append([]Handler{handler}, handlers...)...)
return r
}

// Get registers a route for GET methods that requests a representation
// of the specified resource. Requests using GET should only retrieve data.
func (r *Registering) Get(handler Handler, handlers ...Handler) Register {
r.app.Add([]string{MethodGet}, r.path, handler, handlers...)
return r
return r.Add([]string{MethodGet}, handler, handlers...)
}

// Head registers a route for HEAD methods that asks for a response identical
Expand Down Expand Up @@ -107,15 +107,15 @@ func (r *Registering) Patch(handler Handler, handlers ...Handler) Register {

// Add allows you to specify multiple HTTP methods to register a route.
func (r *Registering) Add(methods []string, handler Handler, handlers ...Handler) Register {
r.app.register(methods, r.path, nil, append([]Handler{handler}, handlers...)...)
r.app.register(methods, r.path, r.group, append([]Handler{handler}, handlers...)...)
return r
}

// Route returns a new Register instance whose route path takes
// RouteChain returns a new Register instance whose route path takes
// the path in the current instance as its prefix.
func (r *Registering) Route(path string) Register {
func (r *Registering) RouteChain(path string) Register {
// Create new group
route := &Registering{app: r.app, path: getGroupPath(r.path, path)}
route := &Registering{app: r.app, group: r.group, path: getGroupPath(r.path, path)}

return route
}
3 changes: 2 additions & 1 deletion router.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ type Router interface {

Group(prefix string, handlers ...Handler) Router

Route(path string) Register
RouteChain(path string) Register
Route(prefix string, fn func(router Router), name ...string) Router

Name(name string) Router
}
Expand Down
Loading