路由
Hono 的路由灵活且直观。 我们一起来看看。
基础用法
ts
// HTTP 方法
app.get('/', (c) => c.text('GET /'))
app.post('/', (c) => c.text('POST /'))
app.put('/', (c) => c.text('PUT /'))
app.delete('/', (c) => c.text('DELETE /'))
// 通配符
app.get('/wild/*/card', (c) => {
return c.text('GET /wild/*/card')
})
// 任意 HTTP 方法
app.all('/hello', (c) => c.text('任意方法 /hello'))
// 自定义 HTTP 方法
app.on('PURGE', '/cache', (c) => c.text('PURGE 方法 /cache'))
// 多个方法
app.on(['PUT', 'DELETE'], '/post', (c) =>
c.text('PUT 或 DELETE /post')
)
// 多个路径
app.on('GET', ['/hello', '/ja/hello', '/en/hello'], (c) =>
c.text('你好')
)路径参数
ts
app.get('/user/:name', async (c) => {
const name = c.req.param('name')
// ...
})或一次性获取全部参数:
ts
app.get('/posts/:id/comment/:comment_id', async (c) => {
const { id, comment_id } = c.req.param()
// ...
})可选参数
ts
// 将匹配 `/api/animal` 和 `/api/animal/:type`
app.get('/api/animal/:type?', (c) => c.text('动物!'))正则表达式
ts
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', async (c) => {
const { date, title } = c.req.param()
// ...
})匹配带斜杠的片段
ts
app.get('/posts/:filename{.+\\.png}', async (c) => {
//...
})链式路由
ts
app
.get('/endpoint', (c) => {
return c.text('GET /endpoint')
})
.post((c) => {
return c.text('POST /endpoint')
})
.delete((c) => {
return c.text('DELETE /endpoint')
})分组
你可以使用另一个 Hono 实例组织路由,再通过 route 方法挂载到主应用。
ts
const book = new Hono()
book.get('/', (c) => c.text('列出书籍')) // GET /book
book.get('/:id', (c) => {
// GET /book/:id
const id = c.req.param('id')
return c.text('查看书籍:' + id)
})
book.post('/', (c) => c.text('创建书籍')) // POST /book
const app = new Hono()
app.route('/book', book)不改变基础路径的分组
你也可以在保持原有基础路径的情况下组合多个实例。
ts
const book = new Hono()
book.get('/book', (c) => c.text('列出书籍')) // GET /book
book.post('/book', (c) => c.text('创建书籍')) // POST /book
const user = new Hono().basePath('/user')
user.get('/', (c) => c.text('列出用户')) // GET /user
user.post('/', (c) => c.text('创建用户')) // POST /user
const app = new Hono()
app.route('/', book) // 处理 /book
app.route('/', user) // 处理 /user基础路径
可以为应用指定基础路径。
ts
const api = new Hono().basePath('/api')
api.get('/book', (c) => c.text('列出书籍')) // GET /api/book搭配主机名的路由
在路径中带上主机名也可以正常工作。
ts
const app = new Hono({
getPath: (req) => req.url.replace(/^https?:\/([^?]+).*$/, '$1'),
})
app.get('/www1.example.com/hello', (c) => c.text('你好 www1'))
app.get('/www2.example.com/hello', (c) => c.text('你好 www2'))使用 host 请求头路由
如果在 Hono 构造函数中设置 getPath(),就能利用 host 请求头进行路由判断。
ts
const app = new Hono({
getPath: (req) =>
'/' +
req.headers.get('host') +
req.url.replace(/^https?:\/\/[^/]+(\/[^?]*).*/, '$1'),
})
app.get('/www1.example.com/hello', (c) => c.text('你好 www1'))
// 以下请求将匹配上面的路由:
// new Request('http://www1.example.com/hello', {
// headers: { host: 'www1.example.com' },
// })利用这一点,你甚至可以根据 User-Agent 等请求头调整路由逻辑。
路由优先级
处理函数和中间件会按照注册顺序执行。
ts
app.get('/book/a', (c) => c.text('a')) // a
app.get('/book/:slug', (c) => c.text('common')) // commonGET /book/a ---> `a`
GET /book/b ---> `common`一旦命中了某个处理函数,请求就会停止继续匹配。
ts
app.get('*', (c) => c.text('common')) // common
app.get('/foo', (c) => c.text('foo')) // fooGET /foo ---> `common` // 不会再派发 foo如果希望中间件优先执行,需要将其写在处理函数之前。
ts
app.use(logger())
app.get('/foo', (c) => c.text('foo'))若想提供“兜底”处理函数,请将其写在其他处理函数之后。
ts
app.get('/bar', (c) => c.text('bar')) // bar
app.get('*', (c) => c.text('fallback')) // fallbackGET /bar ---> `bar`
GET /foo ---> `fallback`分组顺序
需要注意,分组时顺序错误往往不易察觉。 route() 会把第二个参数(例如 three 或 two)中已有的路由加入到自身(two 或 app)的路由表中。
ts
three.get('/hi', (c) => c.text('hi'))
two.route('/three', three)
app.route('/two', two)
export default app这样会返回 200:
GET /two/three/hi ---> `hi`但如果顺序弄错,就会返回 404。
ts
three.get('/hi', (c) => c.text('hi'))
app.route('/two', two) // `two` 尚未注册路由
two.route('/three', three)
export default appGET /two/three/hi ---> 404 Not Found