Skip to content

미들웨어

미들웨어는 핸들러의 앞뒤에서 동작한다. 디스패치 전에 Request를 가져오거나, 디스패치 후에 Response를 조작할 수 있다.

미들웨어 정의

  • 핸들러 - Response 객체를 반환한다. 하나의 핸들러만 호출된다.
  • 미들웨어 - 아무것도 반환하지 않으며, await next()를 통해 다음 미들웨어로 진행된다.

사용자는 핸들러와 마찬가지로 app.useapp.HTTP_METHOD를 사용해 미들웨어를 등록할 수 있다. 이 기능을 통해 경로와 메서드를 쉽게 지정할 수 있다.

ts
// 모든 메서드와 라우트에 매칭
app.use(logger())

// 특정 경로 지정
app.use('/posts/*', cors())

// 메서드와 경로 지정
app.post('/posts/*', basicAuth())

핸들러가 Response를 반환하면, 이를 최종 사용자에게 전달하고 처리를 중단한다.

ts
app.post('/posts', (c) => c.text('Created!', 201))

이 경우, 디스패치 전에 다음과 같이 네 개의 미들웨어가 처리된다:

ts
logger() -> cors() -> basicAuth() -> *handler*

실행 순서

미들웨어의 실행 순서는 미들웨어가 등록된 순서에 따라 결정된다. 첫 번째로 등록된 미들웨어의 next 이전 과정이 가장 먼저 실행되고, next 이후 과정은 가장 나중에 실행된다. 아래 예제를 참고한다.

ts
app.use(async (_, next) => {
  console.log('middleware 1 start')
  await next()
  console.log('middleware 1 end')
})
app.use(async (_, next) => {
  console.log('middleware 2 start')
  await next()
  console.log('middleware 2 end')
})
app.use(async (_, next) => {
  console.log('middleware 3 start')
  await next()
  console.log('middleware 3 end')
})

app.get('/', (c) => {
  console.log('handler')
  return c.text('Hello!')
})

실행 결과는 다음과 같다.

middleware 1 start
  middleware 2 start
    middleware 3 start
      handler
    middleware 3 end
  middleware 2 end
middleware 1 end

내장 미들웨어

Hono는 기본적으로 내장 미들웨어를 제공한다.

ts
import { Hono } from 'hono'
import { poweredBy } from 'hono/powered-by'
import { logger } from 'hono/logger'
import { basicAuth } from 'hono/basic-auth'

const app = new Hono()

app.use(poweredBy())
app.use(logger())

app.use(
  '/auth/*',
  basicAuth({
    username: 'hono',
    password: 'acoolproject',
  })
)

WARNING

Deno 환경에서는 Hono 버전과 다른 버전의 미들웨어를 사용할 수 있지만, 이로 인해 버그가 발생할 수 있다. 예를 들어, 아래 코드는 버전이 달라 정상적으로 동작하지 않는다.

ts
import { Hono } from 'jsr:@hono/hono@4.4.0'
import { upgradeWebSocket } from 'jsr:@hono/hono@4.4.5/deno'

const app = new Hono()

app.get(
  '/ws',
  upgradeWebSocket(() => ({
    // ...
  }))
)

커스텀 미들웨어

app.use() 안에 직접 미들웨어를 작성할 수 있다:

ts
// 커스텀 로거
app.use(async (c, next) => {
  console.log(`[${c.req.method}] ${c.req.url}`)
  await next()
})

// 커스텀 헤더 추가
app.use('/message/*', async (c, next) => {
  await next()
  c.header('x-message', 'This is middleware!')
})

app.get('/message/hello', (c) => c.text('Hello Middleware!'))

하지만 app.use() 안에 직접 미들웨어를 내장하면 재사용성이 제한될 수 있다. 따라서 미들웨어를 별도의 파일로 분리할 수 있다.

contextnext의 타입 정의를 잃지 않으려면, 미들웨어를 분리할 때 Hono의 팩토리에서 제공하는 createMiddleware()를 사용할 수 있다.

ts
import { createMiddleware } from 'hono/factory'

const logger = createMiddleware(async (c, next) => {
  console.log(`[${c.req.method}] ${c.req.url}`)
  await next()
})

INFO

createMiddleware와 함께 타입 제네릭을 사용할 수 있다:

ts
createMiddleware<{Bindings: Bindings}>(async (c, next) =>

응답 수정하기

필요한 경우 미들웨어를 설계해 응답을 수정할 수도 있다:

ts
const stripRes = createMiddleware(async (c, next) => {
  await next()
  c.res = undefined
  c.res = new Response('New Response')
})

미들웨어 인자 내부에서 컨텍스트 접근

미들웨어 인자 내부에서 컨텍스트에 접근하려면 app.use에서 제공하는 컨텍스트 파라미터를 직접 사용한다. 아래 예제를 참고하면 더 명확히 이해할 수 있다.

ts
import { cors } from 'hono/cors'

app.use('*', async (c, next) => {
  const middleware = cors({
    origin: c.env.CORS_ORIGIN,
  })
  return middleware(c, next)
})

미들웨어에서 컨텍스트 확장하기

미들웨어 내부에서 컨텍스트를 확장하려면 c.set을 사용한다. createMiddleware 함수에 { Variables: { yourVariable: YourVariableType } }와 같은 제네릭 인자를 전달하면 타입 안전성을 보장할 수 있다.

ts
import { createMiddleware } from 'hono/factory'

const echoMiddleware = createMiddleware<{
  Variables: {
    echo: (str: string) => string
  }
}>(async (c, next) => {
  c.set('echo', (str) => str)
  await next()
})

app.get('/echo', echoMiddleware, (c) => {
  return c.text(c.var.echo('Hello!'))
})

서드파티 미들웨어

내장 미들웨어는 외부 모듈에 의존하지 않지만, 서드파티 미들웨어는 외부 라이브러리에 의존할 수 있다. 이를 통해 더 복잡한 애플리케이션을 구축할 수 있다.

예를 들어, GraphQL 서버 미들웨어, Sentry 미들웨어, Firebase 인증 미들웨어 등이 있다.

Released under the MIT License.