Skip to content

라우팅

Hono의 라우팅은 유연하고 직관적이다. 한번 살펴보자.

기본

ts
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
// 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('Hello')
)

경로 파라미터

ts
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/user/:name', async (c) => {
  const name = c.req.param('name')
  //       ^?
  // ...
})

또는 모든 파라미터를 한 번에 가져올 수 있다:

ts
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/posts/:id/comment/:comment_id', async (c) => {
  const { id, comment_id } = c.req.param()
  //       ^?
  // ...
})

선택적 파라미터

ts
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
// `/api/animal`과 `/api/animal/:type` 모두 매칭
app.get('/api/animal/:type?', (c) => c.text('Animal!'))

정규 표현식

ts
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/post/:date{[0-9]+}/:title{[a-z]+}', async (c) => {
  const { date, title } = c.req.param()
  //       ^?
  // ...
})

슬래시 포함하기

ts
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/posts/:filename{.+\\.png}', async (c) => {
  //...
})

체인형 라우트

ts
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
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
import { Hono } from 'hono'
// ---cut---
const book = new Hono()

book.get('/', (c) => c.text('List Books')) // GET /book
book.get('/:id', (c) => {
  // GET /book/:id
  const id = c.req.param('id')
  return c.text('Get Book: ' + id)
})
book.post('/', (c) => c.text('Create Book')) // POST /book

const app = new Hono()
app.route('/book', book)

기본 경로를 유지한 채 그룹화하기

기본 경로를 변경하지 않고도 여러 인스턴스를 그룹화할 수 있다.

ts
import { Hono } from 'hono'
// ---cut---
const book = new Hono()
book.get('/book', (c) => c.text('List Books')) // GET /book
book.post('/book', (c) => c.text('Create Book')) // POST /book

const user = new Hono().basePath('/user')
user.get('/', (c) => c.text('List Users')) // GET /user
user.post('/', (c) => c.text('Create User')) // POST /user

const app = new Hono()
app.route('/', book) // /book 처리
app.route('/', user) // /user 처리

기본 경로

기본 경로를 지정할 수 있다.

ts
import { Hono } from 'hono'
// ---cut---
const api = new Hono().basePath('/api')
api.get('/book', (c) => c.text('List Books')) // GET /api/book

호스트명을 이용한 라우팅

호스트명을 포함하면 정상적으로 동작한다.

ts
import { Hono } from 'hono'
// ---cut---
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'))

host 헤더 값을 활용한 라우팅

Hono에서는 getPath() 함수를 Hono 생성자에 설정하면 host 헤더 값을 처리할 수 있다.

ts
import { Hono } from 'hono'
// ---cut---
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'))

// 다음과 같은 요청이 라우트와 일치한다:
// new Request('http://www1.example.com/hello', {
//  headers: { host: 'www1.example.com' },
// })

이를 활용하면, 예를 들어 User-Agent 헤더를 기반으로 라우팅을 변경할 수 있다.

라우팅 우선순위

핸들러나 미들웨어는 등록된 순서대로 실행된다.

ts
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/book/a', (c) => c.text('a')) // a
app.get('/book/:slug', (c) => c.text('common')) // common
GET /book/a ---> `a`
GET /book/b ---> `common`

핸들러가 실행되면 프로세스는 중단된다.

ts
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('*', (c) => c.text('common')) // common
app.get('/foo', (c) => c.text('foo')) // foo
GET /foo ---> `common` // foo는 실행되지 않음

실행하고 싶은 미들웨어가 있다면 핸들러 위에 코드를 작성한다.

ts
import { Hono } from 'hono'
import { logger } from 'hono/logger'
const app = new Hono()
// ---cut---
app.use(logger())
app.get('/foo', (c) => c.text('foo'))

"fallback" 핸들러를 사용하려면 다른 핸들러 아래에 코드를 작성한다.

ts
import { Hono } from 'hono'
const app = new Hono()
// ---cut---
app.get('/bar', (c) => c.text('bar')) // bar
app.get('*', (c) => c.text('fallback')) // fallback
GET /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
import { Hono } from 'hono'
const app = new Hono()
const two = new Hono()
const three = new Hono()
// ---cut---
three.get('/hi', (c) => c.text('hi'))
app.route('/two', two) // `two`에 라우트가 없음
two.route('/three', three)

export default app
GET /two/three/hi ---> 404 Not Found

Released under the MIT License.