Last Updated: 3/9/2026
Routing
Routing is how Hono maps URLs to handlers. Hono’s routing is flexible, intuitive, and blazing fast.
Basic Routes
Define routes using HTTP method functions:
import { Hono } from 'hono'
const app = new Hono()
// GET request
app.get('/', (c) => c.text('GET /'))
// POST request
app.post('/', (c) => c.text('POST /'))
// PUT request
app.put('/', (c) => c.text('PUT /'))
// DELETE request
app.delete('/', (c) => c.text('DELETE /'))All HTTP methods are supported:
app.get()- GETapp.post()- POSTapp.put()- PUTapp.delete()- DELETEapp.patch()- PATCHapp.head()- HEADapp.options()- OPTIONS
Any Method
Handle all HTTP methods for a route:
app.all('/hello', (c) => c.text('Any Method /hello'))Custom Methods
Handle custom HTTP methods:
// Single custom method
app.on('PURGE', '/cache', (c) => c.text('PURGE Method /cache'))
// Multiple methods
app.on(['PUT', 'DELETE'], '/post', (c) =>
c.text('PUT or DELETE /post')
)Multiple Paths
Register the same handler for multiple paths:
app.on('GET', ['/hello', '/ja/hello', '/en/hello'], (c) =>
c.text('Hello')
)Path Parameters
Basic Parameters
Capture dynamic segments in URLs:
app.get('/user/:name', (c) => {
const name = c.req.param('name')
return c.text(`Hello, ${name}!`)
})GET /user/alice → "Hello, alice!"
GET /user/bob → "Hello, bob!"Multiple Parameters
Get all parameters at once:
app.get('/posts/:id/comment/:commentId', (c) => {
const { id, commentId } = c.req.param()
return c.json({ postId: id, commentId })
})GET /posts/123/comment/456
→ { "postId": "123", "commentId": "456" }Type safety: Parameter names are inferred as literal types:
app.get('/posts/:id', (c) => {
const id = c.req.param('id') // ✅ Works
const slug = c.req.param('slug') // ❌ Type error!
})Optional Parameters
Make parameters optional with ?:
// Matches both /api/animal and /api/animal/dog
app.get('/api/animal/:type?', (c) => {
const type = c.req.param('type')
return c.text(`Animal: ${type || 'any'}`)
})GET /api/animal → "Animal: any"
GET /api/animal/dog → "Animal: dog"Pattern Matching
Regular Expressions
Constrain parameters with regex:
// Only match numeric IDs
app.get('/post/:id{[0-9]+}', (c) => {
const id = c.req.param('id')
return c.text(`Post ${id}`)
})GET /post/123 → Matches
GET /post/abc → Does not matchMultiple constraints:
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', (c) => {
const { date, title } = c.req.param()
return c.json({ date, title })
})GET /post/20240101/hello → Matches
GET /post/abc/hello → Does not match
GET /post/20240101/Hello → Does not match (uppercase)Including Slashes
Capture paths including slashes:
// Matches any path ending in .png
app.get('/posts/:filename{.+\\.png}', (c) => {
const filename = c.req.param('filename')
return c.text(`Image: ${filename}`)
})GET /posts/photo.png → Matches
GET /posts/2024/01/photo.png → Matches
GET /posts/2024/01/photo.jpg → Does not matchWildcards
Use * to match any segment:
app.get('/wild/*/card', (c) => {
return c.text('Wildcard matched')
})GET /wild/any/card → Matches
GET /wild/thing/card → Matches
GET /wild/any/thing/card → Does not match (multiple segments)Match everything after a prefix:
app.get('/api/*', (c) => {
return c.text('API endpoint')
})GET /api/users → Matches
GET /api/posts/123 → Matches
GET /api/v1/users/456 → MatchesChained Routes
Chain multiple methods on the same path:
app
.get('/endpoint', (c) => c.text('GET /endpoint'))
.post((c) => c.text('POST /endpoint'))
.delete((c) => c.text('DELETE /endpoint'))This is equivalent to:
app.get('/endpoint', (c) => c.text('GET /endpoint'))
app.post('/endpoint', (c) => c.text('POST /endpoint'))
app.delete('/endpoint', (c) => c.text('DELETE /endpoint'))Grouping Routes
With Hono Instances
Organize routes into separate modules:
// books.ts
import { Hono } from 'hono'
const books = new Hono()
books.get('/', (c) => c.text('List Books')) // GET /book
books.get('/:id', (c) => { // GET /book/:id
const id = c.req.param('id')
return c.text(`Get Book: ${id}`)
})
books.post('/', (c) => c.text('Create Book')) // POST /book
export default books// app.ts
import { Hono } from 'hono'
import books from './books'
const app = new Hono()
app.route('/book', books)
export default appWithout Changing Base
Group multiple instances while keeping their base paths:
const books = new Hono()
books.get('/book', (c) => c.text('List Books')) // GET /book
books.post('/book', (c) => c.text('Create Book')) // POST /book
const users = new Hono().basePath('/user')
users.get('/', (c) => c.text('List Users')) // GET /user
users.post('/', (c) => c.text('Create User')) // POST /user
const app = new Hono()
app.route('/', books) // Handles /book
app.route('/', users) // Handles /userBase Path
Set a base path for all routes:
const api = new Hono().basePath('/api')
api.get('/book', (c) => c.text('List Books')) // GET /api/book
api.get('/user', (c) => c.text('List Users')) // GET /api/userRouting Priority
Handlers execute in registration order:
app.get('/book/a', (c) => c.text('a'))
app.get('/book/:slug', (c) => c.text('common'))GET /book/a → "a"
GET /book/b → "common"Important: Once a handler returns, processing stops:
app.get('*', (c) => c.text('common')) // This runs first
app.get('/foo', (c) => c.text('foo')) // This never runs!GET /foo → "common" (foo handler is never reached)Middleware Before Handlers
Middleware should be registered before handlers:
import { logger } from 'hono/logger'
// ✅ Correct order
app.use(logger()) // Middleware first
app.get('/foo', (c) => c.text('foo')) // Then handlersFallback Handlers
Register fallback handlers after specific routes:
app.get('/bar', (c) => c.text('bar')) // Specific route first
app.get('*', (c) => c.text('fallback')) // Fallback lastGET /bar → "bar"
GET /foo → "fallback"Hostname Routing
With Full URL
Route based on hostname in the URL:
const app = new Hono({
getPath: (req) => req.url.replace(/^https?:\/\/([^?]+).*$/, '$1'),
})
app.get('/www1.example.com/hello', (c) => c.text('hello www1'))
app.get('/www2.example.com/hello', (c) => c.text('hello www2'))With Host Header
Route based on the Host header:
const app = new Hono({
getPath: (req) =>
'/' + req.headers.get('host') +
req.url.replace(/^https?:\/\/[^/]+(\/.*)/, '$1'),
})
app.get('/www1.example.com/hello', (c) => c.text('hello www1'))
// Matches requests with Host: www1.example.com headerUse cases:
- Multi-tenant applications
- Different behavior per subdomain
- User-Agent based routing (using same technique with User-Agent header)
Strict Mode
By default, Hono distinguishes trailing slashes:
const app = new Hono() // strict mode ON by default
app.get('/hello', (c) => c.text('Hello'))GET /hello → Matches
GET /hello/ → Does not matchDisable strict mode to treat them equally:
const app = new Hono({ strict: false })
app.get('/hello', (c) => c.text('Hello'))GET /hello → Matches
GET /hello/ → Also matchesRouter Selection
Hono uses SmartRouter by default, which automatically chooses the best router. You can manually specify a router:
import { Hono } from 'hono'
import { RegExpRouter } from 'hono/router/reg-exp-router'
import { TrieRouter } from 'hono/router/trie-router'
// Use RegExpRouter explicitly
const app = new Hono({ router: new RegExpRouter() })
// Or TrieRouter
const app2 = new Hono({ router: new TrieRouter() })When to specify:
- Usually not needed - SmartRouter picks the best one
- Use
LinearRouterfor serverless with frequent cold starts - Use
PatternRouterfor smallest possible bundle size
See also: Router Internals for performance details.
Common Patterns
REST API
const api = new Hono()
// List resources
api.get('/posts', (c) => c.json({ posts: [] }))
// Get single resource
api.get('/posts/:id', (c) => {
const id = c.req.param('id')
return c.json({ post: { id } })
})
// Create resource
api.post('/posts', async (c) => {
const body = await c.req.json()
return c.json({ post: body }, 201)
})
// Update resource
api.put('/posts/:id', async (c) => {
const id = c.req.param('id')
const body = await c.req.json()
return c.json({ post: { id, ...body } })
})
// Delete resource
api.delete('/posts/:id', (c) => {
const id = c.req.param('id')
return c.json({ deleted: id })
})Versioned API
const v1 = new Hono()
v1.get('/users', (c) => c.json({ version: 'v1' }))
const v2 = new Hono()
v2.get('/users', (c) => c.json({ version: 'v2' }))
const app = new Hono()
app.route('/api/v1', v1)
app.route('/api/v2', v2)Nested Resources
app.get('/posts/:postId/comments', (c) => {
const postId = c.req.param('postId')
return c.json({ comments: [], postId })
})
app.get('/posts/:postId/comments/:commentId', (c) => {
const { postId, commentId } = c.req.param()
return c.json({ comment: { postId, commentId } })
})What’s Next?
- Handle requests: Context and Handlers
- Add middleware: Middleware Concept
- API reference: Routing API