diff --git a/deno_dist/hono.ts b/deno_dist/hono.ts index 6678388ed..0275bc1e9 100644 --- a/deno_dist/hono.ts +++ b/deno_dist/hono.ts @@ -66,7 +66,7 @@ export class Hono< routers: [new RegExpRouter(), new TrieRouter()], }) readonly strict: boolean = true // strict routing - default is true - private basePath: string = '' + private _basePath: string = '' private path: string = '*' routes: RouterRoute[] = [] @@ -134,28 +134,41 @@ export class Hono< private notFoundHandler: NotFoundHandler = notFoundHandler private errorHandler: ErrorHandler = errorHandler + route( + path: SubPath, + app: Hono + ): Hono | S>, BasePath> + /** @deprecated + * Use `basePath` instead of `route` with one argument. + * The `route` with one argument has been removed in v4. + */ + route(path: SubPath): Hono, BasePath> route( path: SubPath, app?: Hono - ): Hono< - E, - RemoveBlankRecord | S>, - MergePath - > { - const subApp = this.clone() - subApp.basePath = mergePath(this.basePath, path) - - if (app) { - app.routes.map((r) => { - const handler = - app.errorHandler === errorHandler - ? r.handler - : async (c: Context, next: Next) => - (await compose([r.handler], app.errorHandler)(c, next)).res - subApp.addRoute(r.method, r.path, handler) - }) + ): Hono | S>, BasePath> { + const subApp = this.basePath(path) + + if (!app) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return subApp as any } + app.routes.map((r) => { + const handler = + app.errorHandler === errorHandler + ? r.handler + : async (c: Context, next: Next) => + (await compose([r.handler], app.errorHandler)(c, next)).res + subApp.addRoute(r.method, r.path, handler) + }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return this as any + } + + basePath(path: SubPath): Hono> { + const subApp = this.clone() + subApp._basePath = mergePath(this._basePath, path) // eslint-disable-next-line @typescript-eslint/no-explicit-any return subApp as any } @@ -181,8 +194,8 @@ export class Hono< private addRoute(method: string, path: string, handler: H) { method = method.toUpperCase() - if (this.basePath) { - path = mergePath(this.basePath, path) + if (this._basePath) { + path = mergePath(this._basePath, path) } this.router.add(method, path, handler) const r: RouterRoute = { path: path, method: method, handler: handler } diff --git a/src/adapter/cloudflare-pages/handler.test.ts b/src/adapter/cloudflare-pages/handler.test.ts index 15bfa11a1..f4a5f8735 100644 --- a/src/adapter/cloudflare-pages/handler.test.ts +++ b/src/adapter/cloudflare-pages/handler.test.ts @@ -39,9 +39,9 @@ describe('Adapter for Cloudflare Pages', () => { expect(await res.text()).toBe('/api/foo') }) - it('Should not use `route()` if path argument is not passed', async () => { + it('Should not use `basePath()` if path argument is not passed', async () => { const request = new Request('http://localhost/api/error') - const app = new Hono().route('/api') + const app = new Hono().basePath('/api') app.onError((e) => { throw e diff --git a/src/adapter/cloudflare-pages/handler.ts b/src/adapter/cloudflare-pages/handler.ts index e6851c115..40044dde5 100644 --- a/src/adapter/cloudflare-pages/handler.ts +++ b/src/adapter/cloudflare-pages/handler.ts @@ -9,14 +9,18 @@ type EventContext = { } interface HandleInterface { - (app: Hono, path?: string): ( - eventContext: EventContext - ) => Response | Promise + ( + app: Hono, + path?: string + ): (eventContext: EventContext) => Response | Promise } export const handle: HandleInterface = - (subApp: Hono, path?: string) => + ( + subApp: Hono, + path?: string + ) => ({ request, env, waitUntil }) => { - const app = path ? new Hono().route(path, subApp) : subApp + const app = path ? new Hono().route(path, subApp as any) : subApp return app.fetch(request, env, { waitUntil, passThroughOnException: () => {} }) } diff --git a/src/adapter/nextjs/handler.test.ts b/src/adapter/nextjs/handler.test.ts index aa65fa3ee..37c0609e6 100644 --- a/src/adapter/nextjs/handler.test.ts +++ b/src/adapter/nextjs/handler.test.ts @@ -27,7 +27,7 @@ describe('Adapter for Next.js', () => { }) it('Should not use `route()` if path argument is not passed', async () => { - const app = new Hono().route('/api') + const app = new Hono().basePath('/api') app.onError((e) => { throw e diff --git a/src/adapter/nextjs/handler.ts b/src/adapter/nextjs/handler.ts index ccbd9c19e..ae48dc4a1 100644 --- a/src/adapter/nextjs/handler.ts +++ b/src/adapter/nextjs/handler.ts @@ -3,12 +3,17 @@ import { Hono } from '../../hono' import type { Env } from '../../types' interface HandleInterface { - (subApp: Hono, path?: string): (req: Request) => Response | Promise + (subApp: Hono, path?: string): ( + req: Request + ) => Response | Promise } export const handle: HandleInterface = - (subApp: Hono, path?: string) => + ( + subApp: Hono, + path?: string + ) => (req) => { - const app = path ? new Hono().route(path, subApp) : subApp + const app = path ? new Hono().route(path, subApp as any) : subApp return app.fetch(req) } diff --git a/src/client/client.test.ts b/src/client/client.test.ts index 7870b6056..98268c3e9 100644 --- a/src/client/client.test.ts +++ b/src/client/client.test.ts @@ -344,8 +344,8 @@ describe('Merge path with `app.route()`', () => { expect(data.ok).toBe(true) }) - it('Should have correct types - route() then get()', async () => { - const base = new Hono().route('/api') + it('Should have correct types - basePath() then get()', async () => { + const base = new Hono().basePath('/api') const app = base.get('/search', (c) => c.jsonT({ ok: true })) type AppType = typeof app const client = hc('http://localhost') diff --git a/src/hono.test.ts b/src/hono.test.ts index d555bbd69..589ad3053 100644 --- a/src/hono.test.ts +++ b/src/hono.test.ts @@ -166,18 +166,18 @@ describe('Routing', () => { it('Nested route', async () => { const app = new Hono() - const book = app.route('/book') + const book = app.basePath('/book') book.get('/', (c) => c.text('get /book')) book.get('/:id', (c) => { return c.text('get /book/' + c.req.param('id')) }) book.post('/', (c) => c.text('post /book')) - const user = app.route('/user') + const user = app.basePath('/user') user.get('/login', (c) => c.text('get /user/login')) user.post('/register', (c) => c.text('post /user/register')) - const appForEachUser = user.route(':id') + const appForEachUser = user.basePath(':id') appForEachUser.get('/profile', (c) => c.text('get /user/' + c.req.param('id') + '/profile')) app.get('/add-path-after-route-call', (c) => c.text('get /add-path-after-route-call')) @@ -214,6 +214,26 @@ describe('Routing', () => { expect(await res.text()).toBe('get /add-path-after-route-call') }) + it('Multiple route', async () => { + const app = new Hono() + + const book = new Hono() + book.get('/hello', (c) => c.text('get /book/hello')) + + const user = new Hono() + user.get('/hello', (c) => c.text('get /user/hello')) + + app.route('/book', book).route('/user', user) + + let res = await app.request('http://localhost/book/hello', { method: 'GET' }) + expect(res.status).toBe(200) + expect(await res.text()).toBe('get /book/hello') + + res = await app.request('http://localhost/user/hello', { method: 'GET' }) + expect(res.status).toBe(200) + expect(await res.text()).toBe('get /user/hello') + }) + describe('Nested route with middleware', () => { const api = new Hono() const api2 = api.use('*', async (_c, next) => await next()) diff --git a/src/hono.ts b/src/hono.ts index 4e9b4e3cf..528c1103c 100644 --- a/src/hono.ts +++ b/src/hono.ts @@ -66,7 +66,7 @@ export class Hono< routers: [new RegExpRouter(), new TrieRouter()], }) readonly strict: boolean = true // strict routing - default is true - private basePath: string = '' + private _basePath: string = '' private path: string = '*' routes: RouterRoute[] = [] @@ -134,28 +134,41 @@ export class Hono< private notFoundHandler: NotFoundHandler = notFoundHandler private errorHandler: ErrorHandler = errorHandler + route( + path: SubPath, + app: Hono + ): Hono | S>, BasePath> + /** @deprecated + * Use `basePath` instead of `route` with one argument. + * The `route` with one argument has been removed in v4. + */ + route(path: SubPath): Hono, BasePath> route( path: SubPath, app?: Hono - ): Hono< - E, - RemoveBlankRecord | S>, - MergePath - > { - const subApp = this.clone() - subApp.basePath = mergePath(this.basePath, path) - - if (app) { - app.routes.map((r) => { - const handler = - app.errorHandler === errorHandler - ? r.handler - : async (c: Context, next: Next) => - (await compose([r.handler], app.errorHandler)(c, next)).res - subApp.addRoute(r.method, r.path, handler) - }) + ): Hono | S>, BasePath> { + const subApp = this.basePath(path) + + if (!app) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return subApp as any } + app.routes.map((r) => { + const handler = + app.errorHandler === errorHandler + ? r.handler + : async (c: Context, next: Next) => + (await compose([r.handler], app.errorHandler)(c, next)).res + subApp.addRoute(r.method, r.path, handler) + }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return this as any + } + + basePath(path: SubPath): Hono> { + const subApp = this.clone() + subApp._basePath = mergePath(this._basePath, path) // eslint-disable-next-line @typescript-eslint/no-explicit-any return subApp as any } @@ -181,8 +194,8 @@ export class Hono< private addRoute(method: string, path: string, handler: H) { method = method.toUpperCase() - if (this.basePath) { - path = mergePath(this.basePath, path) + if (this._basePath) { + path = mergePath(this._basePath, path) } this.router.add(method, path, handler) const r: RouterRoute = { path: path, method: method, handler: handler }