미들웨어
미들웨어는 핸들러의 앞뒤에서 동작한다. 디스패치 전에 Request를 가져오거나, 디스패치 후에 Response를 조작할 수 있다.
미들웨어 정의
- 핸들러 -
Response객체를 반환한다. 하나의 핸들러만 호출된다. - 미들웨어 - 아무것도 반환하지 않으며,
await next()를 통해 다음 미들웨어로 진행된다.
사용자는 핸들러와 마찬가지로 app.use나 app.HTTP_METHOD를 사용해 미들웨어를 등록할 수 있다. 이 기능을 통해 경로와 메서드를 쉽게 지정할 수 있다.
// 모든 메서드와 라우트에 매칭
app.use(logger())
// 특정 경로 지정
app.use('/posts/*', cors())
// 메서드와 경로 지정
app.post('/posts/*', basicAuth())핸들러가 Response를 반환하면, 이를 최종 사용자에게 전달하고 처리를 중단한다.
app.post('/posts', (c) => c.text('Created!', 201))이 경우, 디스패치 전에 다음과 같이 네 개의 미들웨어가 처리된다:
logger() -> cors() -> basicAuth() -> *handler*실행 순서
미들웨어의 실행 순서는 미들웨어가 등록된 순서에 따라 결정된다. 첫 번째로 등록된 미들웨어의 next 이전 과정이 가장 먼저 실행되고, next 이후 과정은 가장 나중에 실행된다. 아래 예제를 참고한다.
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는 기본적으로 내장 미들웨어를 제공한다.
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 버전과 다른 버전의 미들웨어를 사용할 수 있지만, 이로 인해 버그가 발생할 수 있다. 예를 들어, 아래 코드는 버전이 달라 정상적으로 동작하지 않는다.
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() 안에 직접 미들웨어를 작성할 수 있다:
// 커스텀 로거
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() 안에 직접 미들웨어를 내장하면 재사용성이 제한될 수 있다. 따라서 미들웨어를 별도의 파일로 분리할 수 있다.
context와 next의 타입 정의를 잃지 않으려면, 미들웨어를 분리할 때 Hono의 팩토리에서 제공하는 createMiddleware()를 사용할 수 있다.
import { createMiddleware } from 'hono/factory'
const logger = createMiddleware(async (c, next) => {
console.log(`[${c.req.method}] ${c.req.url}`)
await next()
})INFO
createMiddleware와 함께 타입 제네릭을 사용할 수 있다:
createMiddleware<{Bindings: Bindings}>(async (c, next) =>응답 수정하기
필요한 경우 미들웨어를 설계해 응답을 수정할 수도 있다:
const stripRes = createMiddleware(async (c, next) => {
await next()
c.res = undefined
c.res = new Response('New Response')
})미들웨어 인자 내부에서 컨텍스트 접근
미들웨어 인자 내부에서 컨텍스트에 접근하려면 app.use에서 제공하는 컨텍스트 파라미터를 직접 사용한다. 아래 예제를 참고하면 더 명확히 이해할 수 있다.
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 } }와 같은 제네릭 인자를 전달하면 타입 안전성을 보장할 수 있다.
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 인증 미들웨어 등이 있다.