itty-spec를 사용한 타입 안전 API 구축: 계약 우선 접근법

발행: (2025년 12월 15일 오전 06:43 GMT+9)
7 min read
원문: Dev.to

Source: Dev.to

소개

itty-specitty‑router에 타입‑안전하고 계약‑우선적인 API 개발을 제공하는 강력한 라이브러리입니다. 표준 스키마 라이브러리를 사용해 API 계약을 정의하면 다음을 얻을 수 있습니다:

  • 자동 검증
  • 완전한 TypeScript 타입 추론
  • 원활한 OpenAPI 문서 생성
  • 엣지 컴퓨팅 환경과의 호환성

전통적인 API 개발은 종종 다음과 같은 문제를 겪습니다:

  • 라우트 핸들러에 흩어져 있는 수동 검증
  • 실제 런타임 동작과 차이가 나는 타입 정의
  • 잘못된 요청에 대한 일관성 없는 오류 처리
  • 수동 유지보수가 필요한 오래된 문서
  • 핸들러가 문서화된 계약과 일치한다는 런타임 보장이 없음

이러한 문제는 버그, 보안 취약점, 열악한 개발자 경험을 초래합니다. itty-spec는 스키마 정의를 라우트, 검증, 타입, 문서의 단일 진실 원천으로 만들어 이러한 문제를 해결합니다.

계약‑우선 접근 방식

itty-spec는 계약‑우선 워크플로우를 따릅니다:

  • 라우트 등록 – 계약 정의에서 라우트를 자동으로 생성
  • 요청 검증 – 들어오는 모든 데이터를 스키마에 맞게 검증
  • 타입 추론 – 핸들러에 대한 완전한 TypeScript 타입 제공
  • 응답 검증 – 핸들러가 유효한 응답을 반환하도록 보장
  • 문서 생성 – OpenAPI 사양을 자동으로 생성

타입이 지정된 요청 객체

계약을 정의하면 TypeScript가 자동으로 다음 타입을 추론합니다:

  • 경로 매개변수/users/:id와 같은 패턴에서 추출
  • 쿼리 매개변수 – 타입이 지정되고 검증된 쿼리 문자열
  • 요청 헤더 – 검증된 헤더 객체
  • 요청 본문 – 타입이 지정된 요청 페이로드
import { createContract } from 'itty-spec';
import { z } from 'zod';

const contract = createContract({
  getUser: {
    path: '/users/:id',
    query: z.object({
      include: z.enum(['posts', 'comments']).optional(),
    }),
    headers: z.object({
      'x-api-key': z.string(),
    }),
    responses: {
      200: { body: z.object({ id: z.string(), name: z.string() }) },
    },
  },
});
// In your handler, everything is fully typed!
const router = createRouter({
  contract,
  handlers: {
    getUser: async (request) => {
      // request.params.id is typed as string
      // request.query.include is typed as 'posts' | 'comments' | undefined
      // request.validatedHeaders['x-api-key'] is typed as string

      const userId = request.params.id; // string
      const include = request.query.include; // enum | undefined

      return request.json({ id: userId, name: 'John' });
    },
  },
});

타입 시스템은 존재하지 않는 속성에 대한 실수로 접근하는 것을 방지하고, 모든 사용 가능한 필드에 대한 자동 완성을 제공합니다.

검증 파이프라인

itty-spec는 미들웨어 파이프라인을 사용하여 들어오는 요청을 핸들러에 도달하기 에 검증합니다. 검증 순서는 다음과 같습니다:

  1. 경로 매개변수 – 선택적 pathParams 스키마에 대해 추출 및 검증
  2. 쿼리 매개변수 – 파싱 및 검증
  3. 헤더 – 정규화 및 검증
  4. 요청 본문 – JSON에서 파싱하고 검증 (POST/PUT/PATCH에 대해)

단계 중 하나라도 실패하면 요청은 400 상태 코드와 상세 오류 정보를 담아 거부되며, 핸들러 코드에 도달하지 않습니다.

const contract = createContract({
  createUser: {
    path: '/users',
    method: 'POST',
    request: z.object({
      name: z.string().min(1).max(100),
      email: z.string().email(),
      age: z.number().int().min(18).max(120),
    }),
    responses: {
      200: { body: z.object({ id: z.string(), name: z.string() }) },
      400: { body: z.object({ error: z.string(), details: z.any() }) },
    },
  },
});

요청 본문이 스키마와 일치하지 않으면 검증이 자동으로 실패하고, 핸들러는 비즈니스 로직에만 집중할 수 있습니다.

벤더에 구애받지 않는 스키마 지원

itty-specStandard Schema V1 사양을 활용하여 이를 구현하는 모든 스키마 라이브러리를 사용할 수 있게 합니다:

  • Zod (v4) – OpenAPI 생성과 완전 호환
  • Valibot – 검증과 호환 (OpenAPI 지원 예정)
  • ArkType – 검증과 호환 (OpenAPI 지원 예정)
  • Standard Schema V1을 구현하는 다른 모든 라이브러리

이 사양은 다음을 포함하는 통합 인터페이스(StandardSchemaV1)를 정의합니다:

  • ~standard.vendor – 스키마 라이브러리를 식별
  • ~standard.validate() – 표준화된 검증 메서드
  • TypeScript를 통한 타입 추론 기능
// Works with Zod
import { z } from 'zod';
const zodSchema = z.object({ name: z.string() });

// Works with Valibot (once supported)
import * as v from 'valibot';
const valibotSchema = v.object({ name: v.string() });

// Both can be used in contracts
const contract = createContract({
  endpoint: {
    path: '/test',
    request: zodSchema, // or valibotSchema
    responses: { 200: { body: zodSchema } },
  },
});

이 접근 방식은 특정 라이브러리에 대한 락인을 방지하고 마이그레이션을 단순화합니다.

자동 OpenAPI 3.1 생성

itty-spec는 계약에서 직접 완전한 OpenAPI 3.1 사양을 생성할 수 있어, 수동 문서 유지보수를 없애줍니다.

OpenAPI 생성기의 주요 기능

  • 모든 계약 작업에서 스키마 추출
  • :param 경로 구문을 {param}(OpenAPI 표준)으로 변환
  • 응답 스키마를 OpenAPI 응답 객체에 매핑
  • 헤더, 쿼리 매개변수, 요청 본문 포함
  • 레지스트리 시스템을 통해 스키마 중복 제거
import { createOpenApiSpecification } from 'itty-spec/openapi';
import { contract } from './contract
Back to Blog

관련 글

더 보기 »

Cloudflare Workers에서 긴 작업 트리거링

문제: 내 작업이 HTTP에 비해 너무 오래 걸렸다. 나는 관리 UI를 담당하는 Worker를 가지고 있었다. 기능 중 하나는 무거운 background process를 시작하는 버튼이었다—...

NextJS와 함께하는 통합 생성 및 편집 폼

소개 이 가이드에서는 새 데이터를 추가하고 기존 데이터를 업데이트하는 두 가지 작업을 모두 처리할 수 있는 통합 폼을 만들 것입니다. 단일 폼을 사용하면 코드 중복을 줄일 수 있습니다.