Skip to main content

Base Path

The basePath() method allows you to create a new Hono instance with a prefixed base path:
import { Hono } from 'hono'

const app = new Hono()
const api = new Hono().basePath('/api')

api.get('/users', (c) => c.json({ users: [] }))
api.get('/posts', (c) => c.json({ posts: [] }))

// Mount the api routes
app.route('/', api)

// GET /api/users -> {"users": []}
// GET /api/posts -> {"posts": []}
From src/hono-base.ts:247, basePath() creates a new instance with a merged path:
basePath<SubPath extends string>(
  path: SubPath
): Hono<E, S, MergePath<BasePath, SubPath>, MergePath<BasePath, SubPath>> {
  const subApp = this.#clone()
  subApp._basePath = mergePath(this._basePath, path)
  return subApp
}

Mounting Routes with route()

The route() method allows you to mount a Hono instance at a specific path:
import { Hono } from 'hono'

const app = new Hono()
const userApp = new Hono()

userApp.get('/', (c) => c.text('User list'))
userApp.get('/:id', (c) => {
  const id = c.req.param('id')
  return c.text(`User ${id}`)
})

app.route('/users', userApp)

// GET /users -> "User list"
// GET /users/123 -> "User 123"

Implementation

From src/hono-base.ts:208, route() mounts all routes from the sub-app:
route<
  SubPath extends string,
  SubEnv extends Env,
  SubSchema extends Schema,
  SubBasePath extends string,
  SubCurrentPath extends string,
>(
  path: SubPath,
  app: Hono<SubEnv, SubSchema, SubBasePath, SubCurrentPath>
): Hono<E, MergeSchemaPath<SubSchema, MergePath<BasePath, SubPath>> | S, BasePath, CurrentPath> {
  const subApp = this.basePath(path)
  app.routes.map((r) => {
    let handler
    if (app.errorHandler === errorHandler) {
      handler = r.handler
    } else {
      handler = async (c: Context, next: Next) =>
        (await compose([], app.errorHandler)(c, () => r.handler(c, next))).res
      ;(handler as any)[COMPOSED_HANDLER] = r.handler
    }

    subApp.#addRoute(r.method, r.path, handler)
  })
  return this
}

Organizing by Feature

Group related routes into separate modules:
// routes/users.ts
import { Hono } from 'hono'

const users = new Hono()

users.get('/', (c) => c.json({ users: [] }))
users.post('/', (c) => c.json({ message: 'User created' }))
users.get('/:id', (c) => c.json({ user: { id: c.req.param('id') } }))
users.put('/:id', (c) => c.json({ message: 'User updated' }))
users.delete('/:id', (c) => c.json({ message: 'User deleted' }))

export default users
// routes/posts.ts
import { Hono } from 'hono'

const posts = new Hono()

posts.get('/', (c) => c.json({ posts: [] }))
posts.post('/', (c) => c.json({ message: 'Post created' }))
posts.get('/:id', (c) => c.json({ post: { id: c.req.param('id') } }))

export default posts
// index.ts
import { Hono } from 'hono'
import users from './routes/users'
import posts from './routes/posts'

const app = new Hono()

app.route('/users', users)
app.route('/posts', posts)

export default app

Nested Routing

You can nest routes multiple levels deep:
import { Hono } from 'hono'

const app = new Hono()
const api = new Hono()
const v1 = new Hono()

v1.get('/users', (c) => c.json({ version: 'v1', users: [] }))
v1.get('/posts', (c) => c.json({ version: 'v1', posts: [] }))

api.route('/v1', v1)
app.route('/api', api)

// GET /api/v1/users -> {"version": "v1", "users": []}
// GET /api/v1/posts -> {"version": "v1", "posts": []}

Accessing Base Path Information

Use the hono/route helper to access base path information:
import { Hono } from 'hono'
import { basePath, baseRoutePath } from 'hono/route'

const app = new Hono()
const userApp = new Hono()

userApp.get('/:id', (c) => {
  const base = basePath(c)        // e.g., '/users'
  const route = baseRoutePath(c)  // e.g., '/users'
  return c.json({ base, route })
})

app.route('/users', userApp)
From src/helper/route/index.ts:106, basePath() returns the base path with embedded parameters:
const basePathCacheMap: WeakMap<Context, Record<number, string>> = new WeakMap()
export const basePath = (c: Context, index?: number): string => {
  index ??= c.req.routeIndex

  const cache = basePathCacheMap.get(c) || []
  if (typeof cache[index] === 'string') {
    return cache[index]
  }

  let result: string
  const rp = baseRoutePath(c, index)
  if (!/[:*]/.test(rp)) {
    result = rp
  } else {
    const paths = splitRoutingPath(rp)
    const reqPath = c.req.path
    let basePathLength = 0
    for (let i = 0, len = paths.length; i < len; i++) {
      const pattern = getPattern(paths[i], paths[i + 1])
      if (pattern) {
        const re = pattern[2] === true || pattern === '*' ? /[^\/]+/ : pattern[2]
        basePathLength += reqPath.substring(basePathLength + 1).match(re)?.[0].length || 0
      } else {
        basePathLength += paths[i].length
      }
      basePathLength += 1 // for '/'
    }
    result = reqPath.substring(0, basePathLength)
  }

  cache[index] = result
  basePathCacheMap.set(c, cache)

  return result
}

Using Middleware with Groups

Apply middleware to grouped routes:
import { Hono } from 'hono'
import { logger } from 'hono/logger'
import { bearerAuth } from 'hono/bearer-auth'

const app = new Hono()
const api = new Hono()

// Apply middleware to all API routes
api.use('*', logger())
api.use('*', bearerAuth({ token: 'secret' }))

api.get('/users', (c) => c.json({ users: [] }))
api.get('/posts', (c) => c.json({ posts: [] }))

app.route('/api', api)

// All /api/* routes will have logger and bearerAuth

Grouping with use()

The use() method can apply middleware to multiple routes:
import { Hono } from 'hono'

const app = new Hono()

// Apply middleware to all /admin routes
app.use('/admin/*', async (c, next) => {
  // Authentication check
  const token = c.req.header('Authorization')
  if (!token) {
    return c.text('Unauthorized', 401)
  }
  await next()
})

app.get('/admin/users', (c) => c.json({ users: [] }))
app.get('/admin/settings', (c) => c.json({ settings: {} }))
From src/hono-base.ts:157, the use() implementation:
this.use = (arg1: string | MiddlewareHandler<any>, ...handlers: MiddlewareHandler<any>[]) => {
  if (typeof arg1 === 'string') {
    this.#path = arg1
  } else {
    this.#path = '*'
    handlers.unshift(arg1)
  }
  handlers.forEach((handler) => {
    this.#addRoute(METHOD_NAME_ALL, this.#path, handler)
  })
  return this as any
}

Versioned APIs

Organize different API versions:
import { Hono } from 'hono'

const app = new Hono()

// Version 1
const v1 = new Hono()
v1.get('/users', (c) => c.json({ version: 1, users: [] }))

// Version 2 with breaking changes
const v2 = new Hono()
v2.get('/users', (c) => c.json({ 
  version: 2, 
  data: { users: [] },
  meta: { count: 0 }
}))

app.route('/v1', v1)
app.route('/v2', v2)

// GET /v1/users -> version 1 format
// GET /v2/users -> version 2 format

Dynamic Base Paths

Use parameters in base paths for multi-tenant applications:
import { Hono } from 'hono'
import { basePath } from 'hono/route'

const app = new Hono()
const tenantApp = new Hono()

tenantApp.get('/dashboard', (c) => {
  const path = basePath(c) // e.g., '/tenant-123'
  return c.text(`Dashboard for ${path}`)
})

app.route('/:tenant', tenantApp)

// GET /tenant-123/dashboard -> "Dashboard for /tenant-123"
// GET /acme-corp/dashboard -> "Dashboard for /acme-corp"

Mounting External Applications

For mounting non-Hono applications, use mount():
import { Hono } from 'hono'

const app = new Hono()

// Mount another framework's handler
app.mount('/external', externalApp.handler)
From src/hono-base.ts:328, the mount() method:
mount(
  path: string,
  applicationHandler: (request: Request, ...args: any) => Response | Promise<Response>,
  options?: MountOptions
): Hono<E, S, BasePath, CurrentPath> {
  // Handle options
  // ...
  const handler: MiddlewareHandler = async (c, next) => {
    const res = await applicationHandler(replaceRequest(c.req.raw), ...getOptions(c))

    if (res) {
      return res
    }

    await next()
  }
  this.#addRoute(METHOD_NAME_ALL, mergePath(path, '*'), handler)
  return this
}

Best Practices

Group routes by business domain or feature:
app.route('/users', usersRoutes)
app.route('/products', productsRoutes)
app.route('/orders', ordersRoutes)
Establish a clear pattern for your API structure:
app.route('/api/v1/users', v1UsersRoutes)
app.route('/api/v1/posts', v1PostsRoutes)
Share middleware across related routes:
const adminRoutes = new Hono()
adminRoutes.use('*', authMiddleware)
adminRoutes.use('*', adminRoleCheck)

app.route('/admin', adminRoutes)
Each route module should handle a single resource or feature:
// users.ts - only user-related routes
// posts.ts - only post-related routes
// comments.ts - only comment-related routes
Route grouping helps maintain clean, modular code and makes it easier to apply middleware and organize your application.

Build docs developers (and LLMs) love