Overview
Routing in Hono maps HTTP requests to handler functions based on the request method and URL path. Hono provides a simple and intuitive API for defining routes with full type safety.
Defining Routes
Routes are defined using HTTP method helpers on the Hono application instance. Each method corresponds to a standard HTTP verb.
Basic Route Definition
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => {
return c.text('Hello Hono!')
})
app.post('/posts', (c) => {
return c.json({ message: 'Post created' })
})
HTTP Method Handlers
Hono supports all standard HTTP methods through dedicated handler functions:
app.get('/users', (c) => c.text('GET /users'))
app.post('/users', (c) => c.text('POST /users'))
app.put('/users/:id', (c) => c.text('PUT /users/:id'))
app.delete('/users/:id', (c) => c.text('DELETE /users/:id'))
app.patch('/users/:id', (c) => c.text('PATCH /users/:id'))
app.options('/users', (c) => c.text('OPTIONS /users'))
Available Methods
From src/hono-base.ts:104-110:
get!: HandlerInterface<E, 'get', S, BasePath, CurrentPath>
post!: HandlerInterface<E, 'post', S, BasePath, CurrentPath>
put!: HandlerInterface<E, 'put', S, BasePath, CurrentPath>
delete!: HandlerInterface<E, 'delete', S, BasePath, CurrentPath>
options!: HandlerInterface<E, 'options', S, BasePath, CurrentPath>
patch!: HandlerInterface<E, 'patch', S, BasePath, CurrentPath>
all!: HandlerInterface<E, 'all', S, BasePath, CurrentPath>
The all Method
The all method matches any HTTP method:
app.all('/api/*', (c) => {
return c.text('Matches all methods')
})
Path Patterns
Hono supports various path patterns for flexible route matching.
Static Paths
Exact path matching:
app.get('/about', (c) => c.text('About page'))
app.get('/contact', (c) => c.text('Contact page'))
Path Parameters
Capture dynamic segments using :param syntax:
app.get('/users/:id', (c) => {
const id = c.req.param('id')
return c.json({ userId: id })
})
app.get('/posts/:postId/comments/:commentId', (c) => {
const { postId, commentId } = c.req.param()
return c.json({ postId, commentId })
})
Wildcard Paths
Match multiple path segments:
app.get('/files/*', (c) => {
return c.text('Matches /files/any/path/here')
})
The on Method
For custom methods or matching multiple methods, use the on method:
// Single method
app.on('CUSTOM', '/webhook', (c) => {
return c.text('Custom method handler')
})
// Multiple methods
app.on(['GET', 'POST'], '/dual', (c) => {
return c.text(`Handled ${c.req.method}`)
})
// Multiple paths
app.on('GET', ['/path1', '/path2'], (c) => {
return c.text('Multiple paths')
})
From src/hono-base.ts:144-154:
this.on = (method: string | string[], path: string | string[], ...handlers: H[]) => {
for (const p of [path].flat()) {
this.#path = p
for (const m of [method].flat()) {
handlers.map((handler) => {
this.#addRoute(m.toUpperCase(), this.#path, handler)
})
}
}
return this as any
}
Route Grouping
Group routes with a common base path using route:
const api = new Hono()
api.get('/users', (c) => c.json({ users: [] }))
api.get('/posts', (c) => c.json({ posts: [] }))
const app = new Hono()
app.route('/api', api)
// Now accessible at /api/users and /api/posts
From src/hono-base.ts:208-232:
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>
Base Paths
Define a base path for all routes in an instance:
const api = new Hono().basePath('/api/v1')
api.get('/users', (c) => c.text('GET /api/v1/users'))
api.get('/posts', (c) => c.text('GET /api/v1/posts'))
From src/hono-base.ts:247-253:
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
}
Route Matching
Routes are matched in the order they are defined. The first matching route handles the request.
Strict Mode
By default, Hono uses strict mode which distinguishes /path from /path/:
const app = new Hono({ strict: true }) // default
app.get('/users', handler) // Matches /users only
app.get('/posts/', handler) // Matches /posts/ only
Disable strict mode to treat both the same:
const app = new Hono({ strict: false })
app.get('/users', handler) // Matches both /users and /users/
From src/hono-base.ts:46-87:
type HonoOptions<E extends Env> = {
/**
* `strict` option specifies whether to distinguish whether the last path is a directory or not.
*
* @default true
*/
strict?: boolean
/**
* `router` option specifies which router to use.
*/
router?: Router<[H, RouterRoute]>
/**
* `getPath` can handle the host header value.
*/
getPath?: GetPath<E>
}
Chaining Routes
Multiple handlers can be chained on the same path:
app
.get('/users', (c) => c.json({ message: 'Getting users' }))
.post('/users', (c) => c.json({ message: 'Creating user' }))
.put('/users/:id', (c) => c.json({ message: 'Updating user' }))
From src/hono-base.ts:127-141, routes return the app instance for chaining:
allMethods.forEach((method) => {
this[method] = (args1: string | H, ...args: H[]) => {
if (typeof args1 === 'string') {
this.#path = args1
} else {
this.#addRoute(method, this.#path, args1)
}
args.forEach((handler) => {
this.#addRoute(method, this.#path, handler)
})
return this as any
}
})
Access registered routes through the routes property:
const app = new Hono()
app.get('/users', handler)
app.post('/posts', handler)
console.log(app.routes)
// Returns RouterRoute[] with route metadata
From src/types.ts:57-62:
interface RouterRoute {
basePath: string
path: string
method: string
handler: H
}
Best Practices
- Define specific routes before wildcard routes
- Use route grouping for better organization
- Leverage TypeScript for path parameter type safety
- Use
basePath for API versioning
Wildcard routes (*) should be defined last, as they will match any path that hasn’t been matched by previous routes.