itty-spec를 사용한 타입 안전 API 구축: 계약 우선 접근법
Source: Dev.to
소개
itty-spec은 itty‑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는 미들웨어 파이프라인을 사용하여 들어오는 요청을 핸들러에 도달하기 전에 검증합니다. 검증 순서는 다음과 같습니다:
- 경로 매개변수 – 선택적
pathParams스키마에 대해 추출 및 검증 - 쿼리 매개변수 – 파싱 및 검증
- 헤더 – 정규화 및 검증
- 요청 본문 – 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-spec는 Standard 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