Skip to content

Repository files navigation

Typia (Nestia) vs AJV (TypeBox) Validation Benchmark

NestJS 프로젝트에서 DTO 및 내부 로직 Validation 성능을 비교하기 위한 벤치마크 프로젝트.

두 가지 검증 접근 방식을 4단계 복잡도의 DTO에 대해 비교한다:

라이브러리 스키마 정의 방식 검증 코드 생성 시점 Swagger 생성 방식
Typia + Nestia 순수 TypeScript 인터페이스 + Typia tags 컴파일 타임 (ts-patch 트랜스폼) @nestia/sdk swagger CLI
AJV + TypeBox TypeBox 스키마 (JSON Schema 호환 값) 런타임 (ajv.compile() 사전 수행) @nestjs/swagger + 스키마 직접 전달

프로젝트 구조

test-validator/
├── package.json
├── tsconfig.json                  # ts-patch + typia/nestia 트랜스폼 플러그인
├── nest-cli.json
├── nestia.config.ts               # Nestia SDK swagger 생성 설정
├── src/
│   ├── main.ts                    # NestJS bootstrap + 이중 Swagger 설정
│   ├── app.module.ts
│   │
│   ├── shared/
│   │   ├── interfaces/            # Typia용 순수 TypeScript 인터페이스 (4단계)
│   │   │   ├── simple.interface.ts
│   │   │   ├── medium.interface.ts
│   │   │   ├── complex.interface.ts
│   │   │   └── very-complex.interface.ts
│   │   └── test-data/             # 각 레벨별 valid/invalid 테스트 데이터
│   │       ├── simple.data.ts
│   │       ├── medium.data.ts
│   │       ├── complex.data.ts
│   │       └── very-complex.data.ts
│   │
│   ├── typia-module/
│   │   ├── typia.module.ts
│   │   ├── typia.controller.ts    # @TypedBody() + @TypedRoute 사용
│   │   └── validators/
│   │       └── typia-validators.ts  # typia.is<T>() 래퍼
│   │
│   ├── typebox-module/
│   │   ├── typebox.module.ts
│   │   ├── typebox.controller.ts  # @Body() + AJV 수동 검증
│   │   ├── schemas/               # TypeBox 스키마 정의 (4단계)
│   │   │   ├── simple.schema.ts
│   │   │   ├── medium.schema.ts
│   │   │   ├── complex.schema.ts
│   │   │   └── very-complex.schema.ts
│   │   └── validators/
│   │       └── ajv-validators.ts  # AJV compile + validate
│   │
│   └── benchmark/
│       ├── benchmark.module.ts
│       ├── benchmark.controller.ts  # GET /benchmark/run (HTTP 벤치마크)
│       ├── benchmark-runner.ts      # 타이밍 수집 + 통계 계산
│       └── benchmark-reporter.ts    # cli-table3 콘솔 테이블 출력
│
└── benchmark/
    └── run-benchmark.ts             # 독립 CLI 벤치마크 스크립트

DTO 복잡도 4단계

Level 1: Simple — UserLogin

가장 기본적인 DTO. 2개 필드, format + length 제약.

필드 타입 검증 규칙
email string format: "email"
password string minLength: 8, maxLength: 64

검증 포인트: 이메일 포맷 정규식, 문자열 길이 범위


Level 2: Medium — UserProfile

8개 필드. uuid, enum, nullable, optional, array, pattern, date-time 포함.

필드 타입 검증 규칙
id string format: "uuid"
username string minLength: 3, maxLength: 30, pattern: ^[a-zA-Z0-9_]+$
email string format: "email"
role enum "admin" | "user" | "moderator" | "guest"
age number | null uint32, minimum: 13, maximum: 150, nullable
tags string[] 각 항목 minLength: 1, maxLength: 20, 배열 maxItems: 10
bio string? optional, maxLength: 500
createdAt string format: "date-time"

검증 포인트: UUID 포맷, enum 멤버십, nullable union, optional 필드, 배열 요소 + 크기 제약, 정규식 패턴, ISO 8601 날짜


Level 3: Complex — Order

11개 필드. 중첩 객체 (Address, OrderItem[]), union 타입, 다중 format 검증.

필드 타입 검증 규칙
orderId string format: "uuid"
customerId string format: "uuid"
status enum "pending" | "confirmed" | "shipped" | "delivered" | "cancelled"
items OrderItem[] minItems: 1, maxItems: 100
shippingAddress Address 중첩 객체 (street, city, state, zipCode, country)
billingAddress Address | null nullable 중첩 객체
paymentMethod enum "credit_card" | "paypal" | "bank_transfer" | "crypto"
totalAmount number minimum: 0
currency string pattern: ^[A-Z]{3}$
notes string? optional, maxLength: 1000
orderedAt string format: "date-time"

중첩 타입:

  • Address: street, city, state (minLength/maxLength), zipCode (US 우편번호 정규식), country (2자리 코드)
  • OrderItem: productId (uuid), name, quantity (uint32, 1-9999), unitPrice (0 < x < 1000000), discount (0-100)

검증 포인트: 깊은 중첩 객체 검증, 배열 내 객체 각각 검증, nullable union 중첩 객체, 여러 format 동시 검증


Level 4: Very Complex — ApiRequest

20+ 필드. 재귀 타입 (FilterGroup), Record<string, T>, 4단계 이상 깊은 중첩, URI/geo 좌표 검증.

필드 타입 검증 규칙
metadata RequestMetadata requestId(uuid), timestamp(date-time), clientVersion(semver pattern), locale(pattern), timezone
resource string pattern: ^[a-z][a-z0-9_]*(/[a-z][a-z0-9_]*)*$
action enum "read" | "create" | "update" | "delete" | "batch"
priority enum "low" | "normal" | "high" | "critical"
filters FilterGroup? 재귀 타입 — groups?: FilterGroup[]
sort SortField[]? minItems: 1, maxItems: 5
pagination Pagination? page(1-10000), pageSize(1-200)
payload Record<string, unknown>? 임의 key-value
includes string[]? minItems: 1, maxItems: 20, 각 요소 minLength: 1, maxLength: 100
location GeoLocation? latitude(-9090), longitude(-180180), altitude(-500~100000)
callback CallbackConfig? url(format: "uri"), method(enum), headers(Record), retryCount(0-5)
dryRun boolean required
idempotencyKey string? format: "uuid"
tags Record<string, string>? key-value 쌍

재귀 타입 구조 (FilterGroup):

FilterGroup
├── logic: "AND" | "OR"
├── conditions: FilterCondition[]
│   ├── field: string
│   ├── operator: "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "in" | "like"
│   └── value: string | number | boolean | (string | number)[]
└── groups?: FilterGroup[]     ← 재귀 참조
    └── (같은 구조 반복)

검증 포인트: 재귀 타입 검증, Record 타입 검증, 4단계 이상 깊은 중첩, URI 포맷, 지리 좌표 범위, semver 패턴, 복합 union 타입


검증 방식 비교

Typia (컴파일 타임 코드 생성)

TypeScript Interface + Typia Tags
        │
        ▼  [ts-patch transform at compile time]
        │
Optimized JavaScript validation code (inline, no schema parsing)
        │
        ▼  [runtime]
        │
typia.is<T>(data)  →  boolean (직접 타입 체크 코드 실행)
  1. 스키마 정의: 순수 TypeScript interface + typia.tags (예: tags.Format<"email">)
  2. 코드 생성: ts-patch가 컴파일 시 typia/lib/transform 플러그인을 실행하여 각 타입에 대한 검증 코드를 인라인으로 생성
  3. 런타임 검증: 생성된 JavaScript 코드가 직접 실행됨 (스키마 파싱/해석 없음)
  4. Swagger: @nestia/sdk swagger CLI가 TypeScript 소스를 분석하여 OpenAPI 3.1 문서 생성

AJV + TypeBox (런타임 스키마 컴파일)

TypeBox Schema Definition (JSON Schema compatible)
        │
        ▼  [ajv.compile() at startup]
        │
Optimized validation function (JIT compiled from schema)
        │
        ▼  [runtime]
        │
validateFn(data)  →  boolean (스키마에서 생성된 함수 실행)
  1. 스키마 정의: TypeBox의 Type.Object(), Type.String() 등으로 정의 → JSON Schema 객체 생성
  2. 컴파일: 서버 시작 시 ajv.compile(schema)로 스키마를 최적화된 검증 함수로 변환
  3. 런타임 검증: 컴파일된 검증 함수가 실행됨
  4. Swagger: TypeBox 스키마가 JSON Schema이므로 @nestjs/swagger의 @ApiBody({ schema }) 에 직접 전달

벤치마크 설계

시나리오 (16개)

4가지 복잡도 × 2가지 라이브러리 × 2가지 데이터(valid/invalid) = 16개 시나리오

# 복잡도 라이브러리 데이터 설명
1 Simple Typia valid 유효한 UserLogin → true 반환
2 Simple Typia invalid 잘못된 email/password → false 반환
3 Simple AJV valid 유효한 UserLogin → true 반환
4 Simple AJV invalid 잘못된 email/password → false 반환
5 Medium Typia valid 유효한 UserProfile → true 반환
6 Medium Typia invalid 잘못된 uuid/enum/age → false 반환
7 Medium AJV valid 유효한 UserProfile → true 반환
8 Medium AJV invalid 잘못된 uuid/enum/age → false 반환
9 Complex Typia valid 유효한 Order → true 반환
10 Complex Typia invalid 잘못된 중첩 객체/배열 → false 반환
11 Complex AJV valid 유효한 Order → true 반환
12 Complex AJV invalid 잘못된 중첩 객체/배열 → false 반환
13 VeryComplex Typia valid 유효한 ApiRequest → true 반환
14 VeryComplex Typia invalid 잘못된 재귀/Record/geo → false 반환
15 VeryComplex AJV valid 유효한 ApiRequest → true 반환
16 VeryComplex AJV invalid 잘못된 재귀/Record/geo → false 반환

측정 방식

항목 값
Warmup 5,000회 (JIT 최적화 보장)
측정 50,000회
타이밍 process.hrtime.bigint() (나노초 단위)
통계 avg, median, p95, p99, min, max, ops/sec
비교 ratio = AJV avg / Typia avg (>1이면 Typia가 빠름)

공정성 보장

  • AJV: ajv.compile(schema)를 서버 시작 시 사전 수행. 벤치마크에서는 컴파일된 함수만 호출
  • Typia: ts-patch가 컴파일 타임에 코드 생성. 벤치마크에서는 생성된 코드만 실행
  • GC 영향: warmup 5,000회로 JIT 컴파일 및 인라인 캐싱 안정화 후 측정
  • 동일 데이터: 양쪽 모두 완전히 동일한 JavaScript 객체를 검증

Swagger 설정

경로 라이브러리 생성 방식 스키마 출처
/api/typia Typia (Nestia) @nestia/sdk swagger CLI가 TypeScript 소스 분석 → swagger-typia.json 생성 TypeScript interface + Typia tags
/api/typebox AJV (TypeBox) @nestjs/swagger의 SwaggerModule.createDocument() TypeBox 스키마 → @ApiBody({ schema }) 직접 전달
  • Typia Swagger에는 각 인터페이스가 components/schemas에 named 스키마로 등록됨 (예: $ref: "#/components/schemas/UserLogin")
  • TypeBox Swagger에는 각 엔드포인트의 requestBody에 인라인 JSON Schema가 포함됨

실행 방법

1. 설치

npm install        # 의존성 설치 + ts-patch install + typia patch (prepare 스크립트)

2. 빌드

npm run build      # nest build + nestia swagger 생성

3. 벤치마크 실행 (CLI)

npm run benchmark  # 16개 시나리오 실행, 콘솔 테이블 출력

출력 예시:

╔══════════════════════════════════════════════════════╗
║  Typia (Nestia) vs AJV (TypeBox) Validation Benchmark ║
╚══════════════════════════════════════════════════════╝

Sanity Check:
  ✓ Typia Simple (valid): true (expected: true)
  ✓ AJV Simple (valid): true (expected: true)
  ...

=== Comparison Summary ===
┌───────────────┬──────────┬───────────────┬───────────────┬────────────┬──────────┐
│ Complexity    │ Data     │ Typia (avg)   │ AJV (avg)     │ Ratio      │ Winner   │
├───────────────┼──────────┼───────────────┼───────────────┼────────────┼──────────┤
│ Simple        │ valid    │ 76 ns         │ 103 ns        │ 1.36x      │ Typia    │
│ Medium        │ valid    │ 190 ns        │ 484 ns        │ 2.55x      │ Typia    │
│ Complex       │ valid    │ 143 ns        │ 186 ns        │ 1.30x      │ Typia    │
│ VeryComplex   │ valid    │ 655 ns        │ 1.15 μs       │ 1.75x      │ Typia    │
└───────────────┴──────────┴───────────────┴───────────────┴────────────┴──────────┘

4. 서버 실행 + Swagger UI

npm run start

5. HTTP 벤치마크

# 기본 설정 (warmup: 5000, iterations: 50000)
curl http://localhost:3000/benchmark/run

# 커스텀 설정
curl "http://localhost:3000/benchmark/run?iterations=10000&warmup=1000"

6. 엔드포인트 테스트

# Typia - valid
curl -X POST http://localhost:3000/typia/simple \
  -H 'Content-Type: application/json' \
  -d '{"email":"user@example.com","password":"securepassword"}'

# TypeBox - valid
curl -X POST http://localhost:3000/typebox/simple \
  -H 'Content-Type: application/json' \
  -d '{"email":"user@example.com","password":"securepassword"}'

# Typia - invalid (400 에러 + 타입 에러 상세)
curl -X POST http://localhost:3000/typia/simple \
  -H 'Content-Type: application/json' \
  -d '{"email":"bad","password":"x"}'

# TypeBox - invalid (400 에러 + AJV 에러 상세)
curl -X POST http://localhost:3000/typebox/simple \
  -H 'Content-Type: application/json' \
  -d '{"email":"bad","password":"x"}'

주요 의존성

패키지 용도
typia 컴파일 타임 TypeScript 타입 검증 코드 생성
@nestia/core NestJS용 Typia 통합 (@TypedBody, @TypedRoute)
@nestia/sdk Typia 인터페이스에서 Swagger 문서 생성 CLI
@sinclair/typebox TypeScript-first JSON Schema 빌더
ajv + ajv-formats JSON Schema 기반 런타임 검증 엔진
@nestjs/swagger NestJS Swagger 모듈 (TypeBox 엔드포인트용)
ts-patch TypeScript 컴파일러 플러그인 시스템 (Typia 트랜스폼 실행)
cli-table3 + chalk 벤치마크 결과 콘솔 테이블 출력

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages