LabHub

블로그

TypeScript 데이터 검증 2026 — Zod·Valibot·ArkType·TypeBox·Effect Schema 심층 비교 (AI 도구 호출 시대의 런타임 검증)

한국어English日本語

프롤로그 — 모든 API 경계가 스키마를 요구하는 시대

2022년 TypeScript 프로젝트에서 "런타임 검증 뭐 써?"라고 물으면 답은 거의 하나였다. Zod. 그 전엔 Joi, Yup, 또는 직접 짠 타입 가드.

2026년의 같은 질문엔 후보가 다섯이다. 그 사이에 일이 너무 많이 일어났다.

이 흐름이 다섯 진영을 만들었다.

이 글은 2026년 5월 기준 이 라이브러리들을 직접 비교한다. 번들·추론·JSON Schema·비동기·OpenAPI·런타임 비용·생태계의 7축으로. 그리고 같은 User 스키마를 5개 라이브러리로 나란히 짠다.


1장 · 풍경 — 데이터 검증 라이브러리 지도

먼저 분류부터. 모든 게 같은 자리에 있지 않다.

분류대표 도구한 줄 요약
객체 빌더 (체이닝)Zod, Yup, Joiz.string().email() 식. 가장 친숙.
모듈러 함수Valibotstring([email()]). 트리 셰이킹 친화.
TS 문법을 스키마로ArkTypeTS 타입 표현을 그대로 런타임에.
JSON Schema 네이티브TypeBox만든 게 곧 JSON Schema.
함수형 + EffectEffect Schema타입드 에러, 양방향 인코딩.
가벼운 어서션Superstruct, io-ts작고 합성 가능, 약간 옛 스타일.

이 글의 초점은 굵게 갈리는 다섯 — Zod·Valibot·ArkType·TypeBox·Effect Schema — 다. Yup·Joi·Superstruct는 7장에서 짧게 정리한다.

왜 이 다섯인가


2장 · 7축으로 보는 비교 매트릭스

상세 분석으로 들어가기 전, 한눈에 보는 큰 그림.

Zod 4ValibotArkTypeTypeBoxEffect Schema
번들 (코어)중간매우 작음 (모듈러)작음매우 작음큼(Effect 포함)
TS 추론 품질매우 좋음매우 좋음최상 (TS 표현 그대로)좋음매우 좋음
JSON Schema 익스포트외부 패키지 필요외부 패키지 필요빌트인빌트인 (정의 자체가 JSON Schema)빌트인
비동기 검증OO부분X (동기 중심)O
OpenAPI 익스포트zod-to-openapi@valibot/to-json-schema빌트인빌트인 (Fastify·AJV)@effect/schema-openapi
런타임 비용빠름 (4에서 큰 폭 개선)매우 빠름빠름가장 빠름 (AJV 컴파일 시)중간~빠름
생태계가장 큼빠르게 성장중간API/Fastify 진영Effect 사용자

이 표만 보고 결정을 내려선 안 된다. 다음 장부터 각 도구를 정확히 무엇이 되고 안 되는지 짚는다.


3장 · Zod — 표준이 된 이유, 그리고 Zod 4의 반격

Zod는 2020년 등장 이래 사실상 TypeScript 검증의 표준이 됐다. 이유 세 가지.

  1. 체이닝 API의 친숙함z.string().email().min(5) 한 줄로 의도가 드러난다. Joi·Yup에서 온 사람에게 학습 비용이 거의 0.
  2. z.infer 타입 추론 — 스키마에서 TS 타입을 자동 도출. 스키마 = 타입의 진실의 원천.
  3. 생태계 폭발 — tRPC·@hookform/resolvers·OpenAI SDK·LangChain·Next.js Server Actions 등 모두 Zod를 일급으로 받는다.

Zod 3 시대의 약점

그래도 Zod는 두 가지 비판을 오래 받았다.

Zod 4가 바꾼 것

Zod 4는 2024년 10월 베타 공개, 2025년 본격 GA. 큰 변경 셋.

Zod로 같은 User 스키마

import { z } from 'zod'

const User = z.object({
  email: z.string().email(),
  age: z.number().int().min(0).max(150),
  name: z.string().min(1),
})

type User = z.infer<typeof User>

const parsed = User.parse({ email: 'foo@example.com', age: 30, name: 'Foo' })
//    ^ parsed: User

safeParse로 에러를 값으로 받기.

const result = User.safeParse(unknown)
if (!result.success) {
  // result.error is a ZodError; pretty issues at result.error.issues
  console.error(result.error.flatten())
}

Zod의 강점 (2026)

Zod의 약점 (2026)


4장 · Valibot — 함수형 모듈러로 번들을 깎다

Valibot은 2023년 말~2024년 사이 폭발적으로 자란 라이브러리다. 핵심 철학 하나.

"기본을 함수로 짜라. 안 쓰는 코드는 번들에 없다."

Zod가 z.string().email().min(5)라면 Valibot은 함수 합성이다.

import * as v from 'valibot'

const User = v.object({
  email: v.pipe(v.string(), v.email()),
  age: v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(150)),
  name: v.pipe(v.string(), v.minLength(1)),
})

type User = v.InferOutput<typeof User>

const parsed = v.parse(User, { email: 'foo@example.com', age: 30, name: 'Foo' })

v.pipe(...)로 검증자를 합성하고, v.parse/v.safeParse로 적용한다. 함수가 모두 named export — 안 쓴 건 esbuild·Rollup이 떨어낸다.

번들 차이

공식 사이트와 커뮤니티 벤치마크가 보여 주는 그림.

Valibot의 BaseSchema API

Valibot 1.0 GA(2024) 이후 API가 안정화됐다. 주요 함수.

비동기 검증은 별도 v.parseAsync/v.safeParseAsync.

Valibot의 강점

Valibot의 약점


5장 · ArkType — TypeScript 문법을 그대로 스키마로

ArkType은 2024년에 v2를 GA로 내며 주목받았다. 발상이 다르다.

"TypeScript 표현을 문자열로 받아서, 그대로 런타임 스키마로 만든다."

같은 User 스키마.

import { type } from 'arktype'

const User = type({
  email: 'string.email',
  age: 'number.integer >= 0',
  name: 'string > 0',
})

// type 추론: TS가 정확히 그 표현을 이해
type User = typeof User.infer

const out = User({ email: 'foo@example.com', age: 30, name: 'Foo' })
if (out instanceof type.errors) {
  console.error(out.summary)
} else {
  // out is the validated value
}

핵심은 'number.integer >= 0' 같은 표현이 TS 컴파일러에 의해 정확한 타입으로 추론된다는 것. ArkType은 자체 미니 DSL을 만들고, 그 DSL을 TS 템플릿 리터럴 타입으로 파싱한다. 결과는 스키마를 손으로 쓴 TS 타입과 100% 일치.

ArkType의 차별점

ArkType 객체 정의의 다른 모양

문자열만이 아니라 TS 객체 그대로도 받는다.

import { type } from 'arktype'

const Post = type({
  id: 'number.integer',
  title: 'string > 0',
  tags: 'string[]',
  author: {
    id: 'number',
    name: 'string',
  },
  publishedAt: 'Date',
})

ArkType의 강점

ArkType의 약점


6장 · TypeBox — 만든 게 그대로 JSON Schema

TypeBox는 2021년 등장 이후 Fastify 진영에서 사랑받다가, AI 도구 호출 시대에 들어와 다시 주목받았다.

"내가 만든 스키마가 곧 JSON Schema다. AJV로 컴파일하면 가장 빠른 런타임."

같은 User 스키마.

import { Type, Static } from '@sinclair/typebox'
import { Value } from '@sinclair/typebox/value'

const User = Type.Object({
  email: Type.String({ format: 'email' }),
  age: Type.Integer({ minimum: 0, maximum: 150 }),
  name: Type.String({ minLength: 1 }),
})

type User = Static<typeof User>

const ok = Value.Check(User, unknown)
const errors = [...Value.Errors(User, unknown)]

Type.Object({ ... })가 반환하는 값은 JSON Schema 그 자체. JSON.stringify(User)하면 표준 JSON Schema 문서가 나온다.

AJV 결합 — 가장 빠른 런타임

import Ajv from 'ajv'
import addFormats from 'ajv-formats'
const ajv = addFormats(new Ajv())

const check = ajv.compile(User)
const ok = check(value) // 컴파일된 함수 직접 호출 — 매우 빠름

AJV는 JSON Schema를 받아서 검증 함수를 V8 친화적인 코드로 빌드한다. 결과는 일반적으로 가장 빠른 런타임 검증 중 하나다.

TypeBox + Fastify

import Fastify from 'fastify'

const app = Fastify().withTypeProvider<TypeBoxTypeProvider>()

app.post('/users', {
  schema: { body: User, response: { 200: User } },
}, async (req) => {
  // req.body is typed as User automatically
  return req.body
})

스키마가 곧 OpenAPI 응답 명세 + 런타임 검증 + 응답 직렬화까지. 한 번 정의로 네 가지 일을 한다.

AI 도구 호출에서 TypeBox

OpenAI·Anthropic·Gemini의 함수/도구 정의는 JSON Schema다.

const GetWeather = Type.Object({
  city: Type.String(),
  units: Type.Optional(Type.Union([Type.Literal('c'), Type.Literal('f')])),
})

const tool = {
  type: 'function',
  function: {
    name: 'get_weather',
    description: '주어진 도시의 현재 날씨',
    parameters: GetWeather, // 그대로 JSON Schema
  },
}

zod-to-json-schema 같은 변환기를 거치지 않고 직접 사용 가능. 변환 시 잃어버리는 정보(예: format, description)가 없다.

TypeBox의 강점

TypeBox의 약점


7장 · Effect Schema — Effect 생태계의 검증 도구

Effect는 Scala·Haskell 영향을 받은 풀스택 TS 함수형 프레임워크다. Effect Schema는 그 안의 데이터 검증·변환 모듈.

"검증은 양방향 변환이다. 그리고 모든 에러는 타입에 들어간다."

같은 User 스키마.

import { Schema } from 'effect'

const User = Schema.Struct({
  email: Schema.String.pipe(Schema.email()),
  age: Schema.Number.pipe(Schema.int(), Schema.between(0, 150)),
  name: Schema.NonEmptyString,
})

type User = Schema.Schema.Type<typeof User>

const decode = Schema.decodeUnknownSync(User)
const out = decode({ email: 'foo@example.com', age: 30, name: 'Foo' })

decodeUnknownSync는 동기 검증. decodeUnknown 또는 decode는 Effect 값을 반환 — Effect 풀스택 안에서 합성 가능.

Effect Schema의 차별점

양방향 변환 예시

import { Schema } from 'effect'

// JSON string으로 들어오는 Date를 도메인 Date로
const DateFromString = Schema.transform(Schema.String, Schema.Date, {
  decode: (s) => new Date(s),
  encode: (d) => d.toISOString(),
})

const Event = Schema.Struct({
  name: Schema.NonEmptyString,
  at: DateFromString,
})

const decoded = Schema.decodeUnknownSync(Event)({ name: 'Launch', at: '2026-05-14T00:00:00Z' })
//      ^ decoded.at is real Date

Effect Schema의 강점

Effect Schema의 약점


8장 · 같은 스키마, 5가지 라이브러리 — User(email + age + name)

5개를 나란히 본다. 같은 의미의 스키마.

8.1 Zod

import { z } from 'zod'

const User = z.object({
  email: z.string().email(),
  age: z.number().int().min(0).max(150),
  name: z.string().min(1),
})

type User = z.infer<typeof User>

8.2 Valibot

import * as v from 'valibot'

const User = v.object({
  email: v.pipe(v.string(), v.email()),
  age: v.pipe(v.number(), v.integer(), v.minValue(0), v.maxValue(150)),
  name: v.pipe(v.string(), v.minLength(1)),
})

type User = v.InferOutput<typeof User>

8.3 ArkType

import { type } from 'arktype'

const User = type({
  email: 'string.email',
  age: '0 <= number.integer <= 150',
  name: 'string > 0',
})

type User = typeof User.infer

8.4 TypeBox

import { Type, Static } from '@sinclair/typebox'

const User = Type.Object({
  email: Type.String({ format: 'email' }),
  age: Type.Integer({ minimum: 0, maximum: 150 }),
  name: Type.String({ minLength: 1 }),
})

type User = Static<typeof User>

8.5 Effect Schema

import { Schema } from 'effect'

const User = Schema.Struct({
  email: Schema.String.pipe(Schema.email()),
  age: Schema.Number.pipe(Schema.int(), Schema.between(0, 150)),
  name: Schema.NonEmptyString,
})

type User = Schema.Schema.Type<typeof User>

비교 — 같은 스키마, 다른 철학

라이브러리줄 수문법 스타일추론 호출
Zod짧음체이닝z.infer
Valibot중간함수 합성 (pipe)v.InferOutput
ArkType가장 짧음TS DSL 문자열typeof X.infer
TypeBox중간Type 빌더Static
Effect Schema중간.pipe 체이닝Schema.Schema.Type

읽기 쉬움은 Zod·ArkType이 강하고, 번들·트리 셰이킹은 Valibot·TypeBox가 강하고, 변환·에러 타입까지 챙기면 Effect Schema가 강하다.


9장 · AI 도구 호출 시대 — JSON Schema가 강제된다

2024~2025 사이 가장 큰 흐름. 모든 주요 LLM이 도구 정의를 JSON Schema로 받는다.

9.1 OpenAI function calling

const tools = [{
  type: 'function',
  function: {
    name: 'get_weather',
    description: 'Get the current weather in a given city',
    parameters: {
      type: 'object',
      properties: {
        city: { type: 'string' },
        units: { type: 'string', enum: ['c', 'f'] },
      },
      required: ['city'],
    },
  },
}]

parameters는 표준 JSON Schema. 객체 직접 작성하거나, TS 스키마 라이브러리에서 변환해야 한다.

9.2 Anthropic tool use

const tools = [{
  name: 'get_weather',
  description: 'Get the current weather in a given city',
  input_schema: {
    type: 'object',
    properties: {
      city: { type: 'string' },
      units: { type: 'string', enum: ['c', 'f'] },
    },
    required: ['city'],
  },
}]

input_schema 키만 다를 뿐 같은 모양.

9.3 라이브러리별 JSON Schema 변환

9.4 변환 시 잃어버리는 것

JSON Schema는 TS 표현보다 한정적이다. 변환 시 종종 잃는 것들.

9.5 OpenAI structured outputs와 strict 모드

OpenAI 2024 후반에 등장한 structured outputs는 JSON Schema 일부 제약(예: additionalProperties: false, 필수 필드, $ref 미지원 등)을 강제한다. TS 라이브러리가 만드는 JSON Schema가 그 제약을 깨면 OpenAI 측이 거부한다.

실무 팁.


10장 · 7축 상세 비교

10.1 번들 크기

순수 코어를 작은 스키마 하나 정도만 쓰는 가정.

라이브러리작은 사용큰 사용비고
Valibot가장 작음 (수백 B)작음트리 셰이킹의 정점
TypeBox매우 작음작음Type.* 자체가 가벼움
ArkType작음중간파서가 함께 들어옴
Zod 4중간 (큰 폭 감소)중간v4-mini는 더 작음
Zod 3트리 셰이킹 약함
Effect SchemaEffect 코어 포함
Yup중간중간
Joi브라우저 친화도 낮음

10.2 TypeScript 추론 품질

라이브러리추론 품질비고
ArkType최상TS 표현 그대로
Zod 4매우 좋음z.infer 광범위
Valibot매우 좋음InferOutput/InferInput
Effect Schema매우 좋음양방향 타입까지
TypeBox좋음Static
Yup보통추론은 약함
Joi약함별도 타입 작성 필요

10.3 JSON Schema 익스포트

라이브러리지원방식
TypeBox네이티브정의 자체
ArkType빌트인.toJsonSchema()
Effect Schema빌트인JSONSchema.make()
Valibot공식 외부@valibot/to-json-schema
Zod외부zod-to-json-schema, @asteasolutions/zod-to-openapi
Yup빈약비공식 변환기
Joi없음직접 작성

10.4 비동기 검증

라이브러리지원패턴
Zod강함z.string().refine(async ...) + parseAsync
Valibot강함parseAsync/safeParseAsync
Effect Schema강함Effect 자체가 비동기
ArkType부분동기 중심
TypeBox약함동기 검증 위주

10.5 OpenAPI 익스포트

라이브러리지원도구
TypeBox매우 좋음Fastify + Type Provider
ArkType좋음.toJsonSchema() 직결
Zod좋음@asteasolutions/zod-to-openapi, @hono/zod-openapi
Effect Schema좋음Effect HTTP·OpenAPI 어댑터
Valibot좋음@valibot/to-json-schema

10.6 런타임 비용 (대략, 핫 패스 기준)

라이브러리검증 속도비고
TypeBox + AJV (compile)가장 빠름V8 친화 컴파일
Valibot매우 빠름함수 합성 단순
ArkType빠름컴파일된 검증자
Zod 4빠름큰 폭 개선
Effect Schema중간~빠름Effect 오버헤드
Zod 3보통깊은 객체 약함
Joi느림무거운 옛 API

(정확한 수치는 케이스마다 다름. 각 라이브러리 공식 벤치마크와 자신의 워크로드에서 측정 권장.)

10.7 생태계 (어댑터·통합)

라이브러리tRPCRHFOpenAI/Anthropic SDKFastifyHonoNext.js
Zod일급일급일급가능가능일급
Valibot지원일급 (@hookform/resolvers/valibot)외부 변환가능가능가능
ArkType지원지원외부 변환가능가능가능
TypeBox부분부분직접일급일급 (@hono/typebox-validator)가능
Effect Schema부분부분외부 변환가능가능가능

11장 · Yup·Joi·Superstruct — 살아 있지만

11.1 Yup

Formik 시대의 표준. 폼 검증에서 여전히 쓰이지만, 신규 채택은 줄어든다. Zod·Valibot에 비해 TS 추론이 약하고 번들이 무거움.

import * as yup from 'yup'

const User = yup.object({
  email: yup.string().email().required(),
  age: yup.number().integer().min(0).max(150).required(),
  name: yup.string().min(1).required(),
})

11.2 Joi

Node 백엔드의 옛 표준. 브라우저 친화도 낮음(번들 큼, 의존성 깊음). 서버 검증에서 레거시 코드베이스에 많이 남아 있다.

const Joi = require('joi')
const User = Joi.object({
  email: Joi.string().email().required(),
  age: Joi.number().integer().min(0).max(150).required(),
  name: Joi.string().min(1).required(),
})

11.3 Superstruct

작고 가벼운 검증. io-ts보다 친절하지만, Zod·Valibot에 비하면 에코시스템이 작다. 작은 프로젝트·라이브러리 내부 검증에는 여전히 좋은 선택.

import { object, string, number, refine } from 'superstruct'

const Email = refine(string(), 'email', (v) => /.+@.+/.test(v))
const User = object({
  email: Email,
  age: number(),
  name: string(),
})

11.4 io-ts

전통 함수형 진영. Effect Schema가 그 자리를 빠르게 가져가고 있다. 새 코드에서 io-ts를 자발적으로 시작하는 경우는 줄어든다.


12장 · Standard Schema — 라이브러리 간 호환

2024년 말~2025년 사이 등장한 Standard Schema 스펙은 검증 라이브러리들이 합의한 최소 인터페이스다. tRPC·Hono·React Hook Form 같은 도구가 한 어댑터만으로 Zod·Valibot·ArkType을 받게 하려는 시도.

핵심 인터페이스(간단화).

interface StandardSchemaV1<Input = unknown, Output = Input> {
  '~standard': {
    version: 1
    vendor: string
    validate: (value: unknown) => StandardSchemaV1.Result<Output> | Promise<StandardSchemaV1.Result<Output>>
    types?: { input: Input; output: Output }
  }
}

Zod·Valibot·ArkType·Effect Schema 모두 이 인터페이스를 구현한다. 결과 — 라이브러리 골라잡기 비용이 더 낮아졌다. 한 코드베이스에 여러 라이브러리가 공존하거나, 다른 도구로 갈아탈 때의 비용이 줄었다.

다만 Standard Schema는 최소 공통일 뿐 라이브러리 고유 기능(transform, refine, brand 등)은 어댑터 작성자가 해야 할 일이 남는다.


13장 · 무엇을 언제 골라야 하나 — 솔직한 결론

자, 정답은 항상 그렇듯 "상황 따라 다르다". 다만 패턴이 있다.

Zod 4를 고를 때

Valibot을 고를 때

ArkType을 고를 때

TypeBox를 고를 때

Effect Schema를 고를 때

Yup·Joi·Superstruct를 고를 때

안티 체크리스트 (피해야 할 패턴)


에필로그 — 스키마는 API 경계의 진실

검증 라이브러리 선택은 얼마나 작은가, 얼마나 친숙한가, 얼마나 멀리 갈 수 있는가의 트레이드오프다. 한쪽이 우월하지 않다. 다만 2026년 5월의 풍경은 분명하다.

결정 체크리스트

안티 패턴

  1. 측정 없이 라이브러리 갈아타기 — 결정은 측정 후.
  2. AI 도구에 변환 결과를 보지 않고 보내기 — additionalProperties·required 누락이 잦음.
  3. JSON Schema로 표현 못 하는 refine을 핵심 검증으로 — 변환 시 사라짐.
  4. 폼 검증에 TypeBox — RHF 어댑터가 약함. Zod·Valibot이 자연스러움.
  5. 큰 객체에 매 요청마다 z.parse 반복 — 핫 패스라면 컴파일된 검증자(TypeBox + AJV) 고려.
  6. 한 스키마 두 진실의 원천 — DB 스키마와 검증 스키마가 다른 곳에 정의되어 미스매치.
  7. 에러 메시지 사용자화 안 하기 — 라이브러리 기본 에러 메시지가 사용자에게 노출됨.

다음 글 예고

다음 글 후보: AI 도구 호출 JSON Schema 디버깅 — OpenAI structured outputs·Anthropic tool use 실수 모음, Zod 4 마이그레이션 후기 — 코드베이스 5만 줄 한 달 작업, Valibot 깊게 — 트리 셰이커가 실제로 어떻게 떨어내는가.

"검증은 코드의 경계다. 경계가 명확한 코드만큼 멀리 간다."

— TypeScript 데이터 검증 2026, 끝.


참고 / References

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다