Skip to content

유효성 검증

Hono는 매우 간단한 유효성 검사 기능만 제공한다. 하지만, 이를 서드파티 유효성 검사 도구와 함께 사용하면 강력한 기능을 발휘할 수 있다. 또한, RPC 기능을 통해 API 스펙을 타입으로 클라이언트와 공유할 수 있다.

수동 유효성 검사

먼저, 외부 라이브러리를 사용하지 않고 들어오는 값을 검증하는 방법을 소개한다.

hono/validator에서 validator를 가져온다.

ts
import { validator } from 'hono/validator'

폼 데이터를 검증하려면 첫 번째 인자로 form을 지정하고, 두 번째 인자로 콜백 함수를 전달한다. 콜백 함수에서 값을 검증하고, 검증된 값을 반환한다. validator는 미들웨어로 사용할 수 있다.

ts
app.post(
  '/posts',
  validator('form', (value, c) => {
    const body = value['body']
    if (!body || typeof body !== 'string') {
      return c.text('Invalid!', 400)
    }
    return {
      body: body,
    }
  }),
  //...

핸들러 내부에서 c.req.valid('form')을 사용해 검증된 값을 가져올 수 있다.

ts
, (c) => {
  const { body } = c.req.valid('form')
  // ... 작업 수행
  return c.json(
    {
      message: 'Created!',
    },
    201
  )
}

검증 대상은 form 외에도 json, query, header, param, cookie가 있다.

WARNING

json을 검증할 때는 요청에 반드시 Content-Type: application/json 헤더가 포함되어야 한다. 그렇지 않으면 요청 본문이 파싱되지 않고 경고가 발생한다.

app.request()를 사용해 테스트할 때 content-type 헤더를 설정하는 것이 중요하다.

다음과 같은 애플리케이션이 있다고 가정하자.

ts
const app = new Hono()
app.post(
  '/testing',
  validator('json', (value, c) => {
    // 통과 검증기
    return value
  }),
  (c) => {
    const body = c.req.valid('json')
    return c.json(body)
  }
)

테스트는 다음과 같이 작성할 수 있다.

ts
// ❌ 동작하지 않음
const res = await app.request('/testing', {
  method: 'POST',
  body: JSON.stringify({ key: 'value' }),
})
const data = await res.json()
console.log(data) // undefined

// ✅ 정상 동작
const res = await app.request('/testing', {
  method: 'POST',
  body: JSON.stringify({ key: 'value' }),
  headers: new Headers({ 'Content-Type': 'application/json' }),
})
const data = await res.json()
console.log(data) // { key: 'value' }

WARNING

header를 검증할 때는 소문자를 키로 사용해야 한다.

Idempotency-Key 헤더를 검증하려면 idempotency-key를 키로 사용해야 한다.

ts
// ❌ 동작하지 않음
app.post(
  '/api',
  validator('header', (value, c) => {
    // idempotencyKey는 항상 undefined
    // 따라서 이 미들웨어는 예상치 못하게 항상 400을 반환
    const idempotencyKey = value['Idempotency-Key']

    if (idempotencyKey == undefined || idempotencyKey === '') {
      throw HTTPException(400, {
        message: 'Idempotency-Key is required',
      })
    }
    return { idempotencyKey }
  }),
  (c) => {
    const { idempotencyKey } = c.req.valid('header')
    // ...
  }
)

// ✅ 정상 동작
app.post(
  '/api',
  validator('header', (value, c) => {
    // 헤더 값을 정상적으로 가져올 수 있음
    const idempotencyKey = value['idempotency-key']

    if (idempotencyKey == undefined || idempotencyKey === '') {
      throw HTTPException(400, {
        message: 'Idempotency-Key is required',
      })
    }
    return { idempotencyKey }
  }),
  (c) => {
    const { idempotencyKey } = c.req.valid('header')
    // ...
  }
)

여러 개의 검증기 사용

요청의 다른 부분을 검증하기 위해 여러 개의 검증기를 포함할 수도 있다:

ts
app.post(
  '/posts/:id',
  validator('param', ...),
  validator('query', ...),
  validator('json', ...),
  (c) => {
    //...
  }

Zod 활용하기

Zod는 대표적인 타사 검증 도구 중 하나다. 타사 검증 도구를 사용하는 것을 권장한다.

Npm 레지스트리에서 설치한다.

sh
npm i zod
sh
yarn add zod
sh
pnpm add zod
sh
bun add zod

zod에서 z를 불러온다.

ts
import { z } from 'zod'

스키마를 작성한다.

ts
const schema = z.object({
  body: z.string(),
})

콜백 함수에서 스키마를 사용해 검증하고, 검증된 값을 반환한다.

ts
const route = app.post(
  '/posts',
  validator('form', (value, c) => {
    const parsed = schema.safeParse(value)
    if (!parsed.success) {
      return c.text('Invalid!', 401)
    }
    return parsed.data
  }),
  (c) => {
    const { body } = c.req.valid('form')
    // ... 작업 수행
    return c.json(
      {
        message: 'Created!',
      },
      201
    )
  }
)

Zod Validator Middleware

Zod Validator Middleware를 사용하면 더 쉽게 작업할 수 있다.

sh
npm i @hono/zod-validator
sh
yarn add @hono/zod-validator
sh
pnpm add @hono/zod-validator
sh
bun add @hono/zod-validator

그리고 zValidator를 불러온다.

ts
import { zValidator } from '@hono/zod-validator'

이후 다음과 같이 작성한다.

ts
const route = app.post(
  '/posts',
  zValidator(
    'form',
    z.object({
      body: z.string(),
    })
  ),
  (c) => {
    const validated = c.req.valid('form')
    // ... 유효성 검사된 데이터 사용
  }
)

Released under the MIT License.