Skip to content

Commit cd273d2

Browse files
authored
Update RouteChain function (#3761)
* docs: clarify route helper diff * Adjust Route docs to drop v2 mention and update Express links * Harden route helpers and keep group context * Clarify RouteChain diff in what's new guide * docs: show RouteChain signature in migration notes * Add coverage for Route helpers * Clarify route chaining release wording
1 parent 198e7d7 commit cd273d2

File tree

8 files changed

+166
-47
lines changed

8 files changed

+166
-47
lines changed

app.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -905,15 +905,35 @@ func (app *App) Group(prefix string, handlers ...Handler) Router {
905905
return grp
906906
}
907907

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

914915
return route
915916
}
916917

918+
// Route is used to define routes with a common prefix inside the supplied
919+
// function. It mirrors the legacy helper and reuses the Group method to create
920+
// a sub-router.
921+
func (app *App) Route(prefix string, fn func(router Router), name ...string) Router {
922+
if fn == nil {
923+
panic("route handler 'fn' cannot be nil")
924+
}
925+
// Create new group
926+
group := app.Group(prefix)
927+
if len(name) > 0 {
928+
group.Name(name[0])
929+
}
930+
931+
// Define routes
932+
fn(group)
933+
934+
return group
935+
}
936+
917937
// Error makes it compatible with the `error` interface.
918938
func (e *Error) Error() string {
919939
return e.Message

app_test.go

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1360,13 +1360,13 @@ func Test_App_Group(t *testing.T) {
13601360
require.Equal(t, 200, resp.StatusCode, "Status code")
13611361
}
13621362

1363-
func Test_App_Route(t *testing.T) {
1363+
func Test_App_RouteChain(t *testing.T) {
13641364
t.Parallel()
13651365
dummyHandler := testEmptyHandler
13661366

13671367
app := New()
13681368

1369-
register := app.Route("/test").
1369+
register := app.RouteChain("/test").
13701370
Get(dummyHandler).
13711371
Head(dummyHandler).
13721372
Post(dummyHandler).
@@ -1387,7 +1387,7 @@ func Test_App_Route(t *testing.T) {
13871387
testStatus200(t, app, "/test", MethodTrace)
13881388
testStatus200(t, app, "/test", MethodPatch)
13891389

1390-
register.Route("/v1").Get(dummyHandler).Post(dummyHandler)
1390+
register.RouteChain("/v1").Get(dummyHandler).Post(dummyHandler)
13911391

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

1400-
register.Route("/v1").Route("/v2").Route("/v3").Get(dummyHandler).Trace(dummyHandler)
1400+
register.RouteChain("/v1").RouteChain("/v2").RouteChain("/v3").Get(dummyHandler).Trace(dummyHandler)
14011401

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

1411+
func Test_App_Route(t *testing.T) {
1412+
t.Parallel()
1413+
1414+
app := New()
1415+
1416+
app.Route("/test", func(api Router) {
1417+
api.Get("/foo", testEmptyHandler).Name("foo")
1418+
1419+
api.Route("/bar", func(bar Router) {
1420+
bar.Get("/", testEmptyHandler).Name("index")
1421+
}, "bar.")
1422+
}, "test.")
1423+
1424+
testStatus200(t, app, "/test/foo", MethodGet)
1425+
1426+
resp, err := app.Test(httptest.NewRequest(MethodGet, "/test/bar/", nil))
1427+
require.NoError(t, err, "app.Test(req)")
1428+
require.Equal(t, http.StatusOK, resp.StatusCode, "Status code")
1429+
1430+
require.Equal(t, "/test/foo", app.GetRoute("test.foo").Path)
1431+
require.Equal(t, "/test/bar/", app.GetRoute("test.bar.index").Path)
1432+
}
1433+
1434+
func Test_App_Route_nilFuncPanics(t *testing.T) {
1435+
t.Parallel()
1436+
1437+
app := New()
1438+
1439+
require.PanicsWithValue(t, "route handler 'fn' cannot be nil", func() {
1440+
app.Route("/panic", nil)
1441+
})
1442+
}
1443+
1444+
func Test_Group_Route_nilFuncPanics(t *testing.T) {
1445+
t.Parallel()
1446+
1447+
app := New()
1448+
grp := app.Group("/api")
1449+
1450+
require.PanicsWithValue(t, "route handler 'fn' cannot be nil", func() {
1451+
grp.Route("/panic", nil)
1452+
})
1453+
}
1454+
1455+
func Test_Group_RouteChain_All(t *testing.T) {
1456+
t.Parallel()
1457+
1458+
app := New()
1459+
var calls []string
1460+
grp := app.Group("/api", func(c Ctx) error {
1461+
calls = append(calls, "group")
1462+
return c.Next()
1463+
})
1464+
1465+
grp.RouteChain("/users").All(func(c Ctx) error {
1466+
calls = append(calls, "routechain")
1467+
return c.SendStatus(http.StatusOK)
1468+
})
1469+
1470+
resp, err := app.Test(httptest.NewRequest(MethodGet, "/api/users", nil))
1471+
require.NoError(t, err, "app.Test(req)")
1472+
require.Equal(t, http.StatusOK, resp.StatusCode, "Status code")
1473+
require.Equal(t, []string{"group", "routechain"}, calls)
1474+
}
1475+
14111476
func Test_App_Deep_Group(t *testing.T) {
14121477
t.Parallel()
14131478
runThroughCount := 0

docs/api/app.md

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,14 @@ func handler(c fiber.Ctx) error {
138138
}
139139
```
140140

141-
### Route
141+
### RouteChain
142142

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

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

147147
```go title="Signature"
148-
func (app *App) Route(path string) Register
148+
func (app *App) RouteChain(path string) Register
149149
```
150150

151151
<details>
@@ -166,7 +166,7 @@ type Register interface {
166166

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

169-
Route(path string) Register
169+
RouteChain(path string) Register
170170
}
171171
```
172172

@@ -184,12 +184,12 @@ import (
184184
func main() {
185185
app := fiber.New()
186186

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

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

204204
// Combine multiple routes
205-
app.Route("/v2").Route("/user").Get(func(c fiber.Ctx) error {
206-
return c.SendString("GET /v2/user")
205+
app.RouteChain("/reports").RouteChain("/daily").Get(func(c fiber.Ctx) error {
206+
return c.SendString("GET /reports/daily")
207207
})
208208

209209
// Use multiple methods
210-
app.Route("/api").Get(func(c fiber.Ctx) error {
210+
app.RouteChain("/api").Get(func(c fiber.Ctx) error {
211211
return c.SendString("GET /api")
212212
}).Post(func(c fiber.Ctx) error {
213213
return c.SendString("POST /api")
@@ -217,6 +217,21 @@ func main() {
217217
}
218218
```
219219

220+
### Route
221+
222+
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.
223+
224+
```go title="Signature"
225+
func (app *App) Route(prefix string, fn func(router Router), name ...string) Router
226+
```
227+
228+
```go title="Example"
229+
app.Route("/test", func(api fiber.Router) {
230+
api.Get("/foo", handler).Name("foo") // /test/foo (name: test.foo)
231+
api.Get("/bar", handler).Name("bar") // /test/bar (name: test.bar)
232+
}, "test.")
233+
```
234+
220235
### HandlersCount
221236

222237
Returns the number of registered handlers.

docs/whats_new.md

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -328,18 +328,17 @@ In `v2` one handler was already mandatory when the route has been registered, bu
328328

329329
### Route chaining
330330

331-
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.
331+
This release 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.
332332

333-
```diff
334-
- Route(prefix string, fn func(router Router), name ...string) Router
335-
+ Route(path string) Register
333+
```go
334+
RouteChain(path string) Register
336335
```
337336

338337
<details>
339338
<summary>Example</summary>
340339

341340
```go
342-
app.Route("/api").Route("/user/:id?")
341+
app.RouteChain("/api").RouteChain("/user/:id?")
343342
.Get(func(c fiber.Ctx) error {
344343
// Get user
345344
return c.JSON(fiber.Map{"message": "Get user", "id": c.Params("id")})
@@ -360,11 +359,11 @@ app.Route("/api").Route("/user/:id?")
360359

361360
</details>
362361

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

365364
### Middleware registration
366365

367-
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.
366+
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.
368367

369368
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.
370369

@@ -1653,7 +1652,7 @@ app.Add([]string{fiber.MethodPost}, "/api", myHandler)
16531652

16541653
#### Mounting
16551654

1656-
In Fiber v3, the `Mount` method has been removed. Instead, you can use the `Use` method to achieve similar functionality.
1655+
In this release, the `Mount` method has been removed. Instead, you can use the `Use` method to achieve similar functionality.
16571656

16581657
```go
16591658
// Before
@@ -1667,7 +1666,7 @@ app.Use("/api", apiApp)
16671666

16681667
#### Route Chaining
16691668

1670-
Refer to the [route chaining](#route-chaining) section for details on migrating `Route`.
1669+
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.
16711670

16721671
```go
16731672
// Before
@@ -1687,7 +1686,7 @@ app.Route("/api", func(apiGrp Router) {
16871686

16881687
```go
16891688
// After
1690-
app.Route("/api").Route("/user/:id?")
1689+
app.RouteChain("/api").RouteChain("/user/:id?")
16911690
.Get(func(c fiber.Ctx) error {
16921691
// Get user
16931692
return c.JSON(fiber.Map{"message": "Get user", "id": c.Params("id")})

group.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,11 +196,30 @@ func (grp *Group) Group(prefix string, handlers ...Handler) Router {
196196
return newGrp
197197
}
198198

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

205205
return register
206206
}
207+
208+
// Route is used to define routes with a common prefix inside the supplied
209+
// function. It mirrors the legacy helper and reuses the Group method to create
210+
// a sub-router.
211+
func (grp *Group) Route(prefix string, fn func(router Router), name ...string) Router {
212+
if fn == nil {
213+
panic("route handler 'fn' cannot be nil")
214+
}
215+
// Create new group
216+
group := grp.Group(prefix)
217+
if len(name) > 0 {
218+
group.Name(name[0])
219+
}
220+
221+
// Define routes
222+
fn(group)
223+
224+
return group
225+
}

middleware/cache/cache_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -875,7 +875,7 @@ func Test_Cache_WithHead(t *testing.T) {
875875
return c.SendString(strconv.Itoa(count))
876876
}
877877

878-
app.Route("/").Get(handler).Head(handler)
878+
app.RouteChain("/").Get(handler).Head(handler)
879879

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

909909
headResp, err := app.Test(httptest.NewRequest(fiber.MethodHead, "/?cache=123", nil))
910910
require.NoError(t, err)

0 commit comments

Comments
 (0)