Skip to content

Commit ee64481

Browse files
committed
feat: support async callbacks in Router.group method and add tests for async grouped routes
1 parent a5a3a0a commit ee64481

8 files changed

Lines changed: 88 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [2.1.2] - [2.1.5] - 2026-03-03
9+
10+
### Added
11+
12+
- Added support for async group callbacks in Express and H3 routers (`await Router.group(...)`).
13+
814
## [2.1.1] - 2026-03-03
915

1016
### Added
@@ -231,7 +237,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
231237

232238
- Optimized route registration performance
233239

234-
## [1.0.0] - 2024-11-01
240+
## [1.0.0] (New API) - 2024-11-01
235241

236242
### Added
237243

docs/API.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,11 @@ Group routes under a common prefix with optional middlewares.
207207
**Parameters:**
208208

209209
- `prefix` (string): URL prefix for all routes in the group
210-
- `callback` (Function): Function containing route definitions
210+
- `callback` (Function): Sync or async function containing route definitions
211211
- `middlewares` (Function[]): Optional middleware functions
212212

213+
**Returns:** Promise<void>
214+
213215
**Example:**
214216

215217
```javascript
@@ -222,6 +224,12 @@ Router.group(
222224
[apiMiddleware],
223225
);
224226

227+
// Async group callback
228+
await Router.group('/api', async () => {
229+
await loadRoutes();
230+
Router.get('/status', handler); // Becomes: /api/status
231+
});
232+
225233
// Nested groups
226234
Router.group('/api', () => {
227235
Router.group('/v1', () => {

docs/EXPRESS.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,15 @@ Router.group('/admin', () => {
182182
});
183183
```
184184

185+
Async group callbacks are also supported:
186+
187+
```javascript
188+
await Router.group('/api', async () => {
189+
await loadRoutes();
190+
Router.get('/status', ({ res }) => res.json({ ok: true }));
191+
});
192+
```
193+
185194
With middleware:
186195

187196
```javascript

docs/H3.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,15 @@ Router.group('/admin', () => {
177177
});
178178
```
179179

180+
Async group callbacks are also supported:
181+
182+
```javascript
183+
await Router.group('/api', async () => {
184+
await loadRoutes();
185+
Router.get('/status', () => ({ ok: true }));
186+
});
187+
```
188+
180189
With middleware:
181190

182191
```javascript

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "clear-router",
3-
"version": "2.1.4",
3+
"version": "2.1.5",
44
"description": "Laravel-style routing system for Express.js and H3, with CommonJS, ESM, and TypeScript support",
55
"keywords": [
66
"h3",

src/express/router.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,11 @@ export class Router {
180180
* @param callback - Function containing route definitions
181181
* @param middlewares - Middleware functions applied to all routes in group
182182
*/
183-
static group (prefix: string, callback: () => void, middlewares?: Middleware[]): void {
183+
static async group (
184+
prefix: string,
185+
callback: () => void | Promise<void>,
186+
middlewares?: Middleware[]
187+
): Promise<void> {
184188
const previousPrefix = this.prefix
185189
const previousMiddlewares = this.groupMiddlewares
186190

@@ -191,10 +195,12 @@ export class Router {
191195
this.prefix = this.normalizePath(fullPrefix)
192196
this.groupMiddlewares = [...previousMiddlewares, ...(middlewares || [])]
193197

194-
callback()
195-
196-
this.prefix = previousPrefix
197-
this.groupMiddlewares = previousMiddlewares
198+
try {
199+
await Promise.resolve(callback())
200+
} finally {
201+
this.prefix = previousPrefix
202+
this.groupMiddlewares = previousMiddlewares
203+
}
198204
}
199205

200206
/**

src/h3/router.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,11 @@ export class Router {
179179
* @param callback - Function containing route definitions
180180
* @param middlewares - Middleware functions applied to all routes in group
181181
*/
182-
static group (prefix: string, callback: () => void, middlewares?: Middleware[]): void {
182+
static async group (
183+
prefix: string,
184+
callback: () => void | Promise<void>,
185+
middlewares?: Middleware[]
186+
): Promise<void> {
183187
const previousPrefix = this.prefix
184188
const previousMiddlewares = this.groupMiddlewares
185189

@@ -190,10 +194,12 @@ export class Router {
190194
this.prefix = this.normalizePath(fullPrefix)
191195
this.groupMiddlewares = [...previousMiddlewares, ...(middlewares || [])]
192196

193-
callback()
194-
195-
this.prefix = previousPrefix
196-
this.groupMiddlewares = previousMiddlewares
197+
try {
198+
await Promise.resolve(callback())
199+
} finally {
200+
this.prefix = previousPrefix
201+
this.groupMiddlewares = previousMiddlewares
202+
}
197203
}
198204

199205
/**

tests/typescript.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,21 @@ describe('Express Routing - TypeScript', () => {
202202
expect(response.body.status).toBe('operational')
203203
})
204204

205+
test('should support async grouped routes', async () => {
206+
await Router.group('/api', async () => {
207+
await new Promise<void>(resolve => setTimeout(resolve, 5))
208+
Router.get('/async-group', ({ res }: HttpContext) => {
209+
res.json({ grouped: true })
210+
})
211+
})
212+
213+
await setupApp()
214+
215+
const response = await request(app).get('/api/async-group')
216+
expect(response.status).toBe(200)
217+
expect(response.body.grouped).toBe(true)
218+
})
219+
205220
test('should handle typed error in middleware', async () => {
206221
const errorMiddleware = (
207222
req: Request,
@@ -436,6 +451,22 @@ describe('H3 Routing - TypeScript', () => {
436451
expect(response.status).toBe('operational')
437452
})
438453

454+
test('should support async grouped routes', async () => {
455+
await H3Router.group('/api', async () => {
456+
await new Promise<void>(resolve => setTimeout(resolve, 5))
457+
H3Router.get('/async-group', () => {
458+
return { grouped: true }
459+
})
460+
})
461+
462+
setupApp()
463+
464+
const response = await router
465+
.fetch(new global.Request(new URL('http://localhost/api/async-group'), { method: 'GET' }))
466+
.then(res => res.json())
467+
expect(response.grouped).toBe(true)
468+
})
469+
439470
test('should handle typed error in middleware', async () => {
440471
const errorMiddleware = (): void => {
441472
throw new HTTPError('Middleware error', { status: 500 })

0 commit comments

Comments
 (0)