OpenAPI (알파)
이 패키지는 알파 버전입니다. API가 예고 없이 변경될 수 있습니다.
@trpc/openapi 패키지는 tRPC 라우터에서 OpenAPI 3.1 사양을 생성합니다. 이 사양을 사용하여 다음을 수행할 수 있습니다:
- 모든 언어에서 타입이 지정된 API 클라이언트 생성
- Postman 또는 Insomnia와 같은 HTTP 도구를 통해 tRPC 엔드포인트 호출
- MCP 서버와 같은 AI 에이전트 통합 활성화
설치
bashpnpm add @trpc/openapi
bashpnpm add @trpc/openapi
AI 코딩 에이전트를 사용하는 경우, 더 나은 코드 생성을 위해 tRPC 스킬을 설치하세요:
bashnpx @tanstack/intent@latest install
bashnpx @tanstack/intent@latest install
@trpc/openapi는 현재 11.x.x-alpha와 같은 방식으로 버전 관리되며, 최신 tRPC v11 버전과 함께 작동해야 하지만, 항상 권장하듯 버전 번호를 일치시키는 것이 좋습니다.
tRPC 설정 조정
생성기는 기존 라우터와 함께 작동하며, 주석이나 데코레이터가 필요하지 않습니다. 다음 사항에 유의하세요:
- 출력 타입 불필요 — 다른 OpenAPI 도구와 달리,
.output()스키마는 선택 사항입니다. 생성기는 구현에서 반환 타입을 자동으로 추론합니다. - 트랜스포머 — 서버에서 데이터 트랜스포머를 사용하는 경우, OpenAPI 클라이언트도 동일한 트랜스포머를 사용해야 합니다. 설정 및 언어 간 옵션에 대해 트랜스포머를 참조하세요.
- 구독 — 현재 생성된 사양에서 제외됩니다. SSE 지원이 계획되어 있습니다.
- 설명 — Zod의
.describe()호출 및 타입, 라우터, 프로시저에 대한 JSDoc 주석은 모두 사양의description필드가 됩니다.
사양 생성
CLI
bashpnpm exec trpc-openapi ./src/server/router.ts
bashpnpm exec trpc-openapi ./src/server/router.ts
| 옵션 | 기본값 | 설명 |
|---|---|---|
-e, --export <name> | AppRouter | 내보낸 라우터 타입의 이름 |
-o, --output <file> | openapi.json | 출력 파일 경로 |
--title <text> | tRPC API | OpenAPI info.title |
--version <ver> | 0.0.0 | OpenAPI info.version |
--server-url <url> | 기본 URL(접두어 포함), 예: https://api.example.com/trpc |
bashpnpm exec trpc-openapi ./src/server/router.ts -o api.json --title "My API" --version 1.0.0 --server-url https://api.example.com/trpc
bashpnpm exec trpc-openapi ./src/server/router.ts -o api.json --title "My API" --version 1.0.0 --server-url https://api.example.com/trpc
프로그래매틱
scripts/generate-openapi.tstsimport { generateOpenAPIDocument } from '@trpc/openapi';const doc = await generateOpenAPIDocument('./src/server/router.ts', {exportName: 'AppRouter',title: 'My API',version: '1.0.0',servers: [{ url: 'https://api.example.com/trpc' }],});
scripts/generate-openapi.tstsimport { generateOpenAPIDocument } from '@trpc/openapi';const doc = await generateOpenAPIDocument('./src/server/router.ts', {exportName: 'AppRouter',title: 'My API',version: '1.0.0',servers: [{ url: 'https://api.example.com/trpc' }],});
생성기는 라우터의 TypeScript 타입을 정적으로 분석하며, 코드를 실행하지 않습니다.
사양에서 클라이언트 생성
모든 OpenAPI 클라이언트 생성기가 작동해야 하지만, 가장 잘 테스트된 통합은 Hey API입니다.
생성된 클라이언트는 tRPC 프로시저와 일치하는 타입이 지정된 SDK 함수를 생성합니다:
- 쿼리 →
GET /procedure.path - 뮤테이션 →
POST /procedure.path - 구독은 무시됩니다(SSE가 곧 출시 예정)
Hey API (TypeScript)
bashpnpm add @trpc/openapi @hey-api/openapi-ts
bashpnpm add @trpc/openapi @hey-api/openapi-ts
기본적으로 OpenAPI에서 생성된 클라이언트는 트랜스포머 설정이나 쿼리 매개변수 인코딩 방식을 알지 못합니다. @trpc/openapi/heyapi 패키지는 이 차이를 메우는 configureTRPCHeyApiClient 헬퍼를 제공합니다. 이 헬퍼는 요청 직렬화, 응답 파싱, 오류 역직렬화를 구성하여 생성된 SDK가 tRPC 엔드포인트와 올바르게 작동하도록 합니다.
트랜스포머 없이
이 경우 Hey API의 CLI 또는 프로그래매틱 API를 사용하여 클라이언트를 생성할 수 있습니다.
bashpnpm exec openapi-ts -i openapi.json -o ./generated
bashpnpm exec openapi-ts -i openapi.json -o ./generated
다음으로 런타임에서 약간의 설정이 필요합니다:
src/usage.tstsimport { configureTRPCHeyApiClient } from '@trpc/openapi/heyapi';import { client } from './generated/client.gen';import { Sdk } from './generated/sdk.gen';configureTRPCHeyApiClient(client, {baseUrl: 'http://localhost:3000',});const sdk = new Sdk({ client });const result = await sdk.greeting({ query: { input: { name: 'World' } } });const user = await sdk.user.create({ body: { name: 'Bob', age: 30 } });
src/usage.tstsimport { configureTRPCHeyApiClient } from '@trpc/openapi/heyapi';import { client } from './generated/client.gen';import { Sdk } from './generated/sdk.gen';configureTRPCHeyApiClient(client, {baseUrl: 'http://localhost:3000',});const sdk = new Sdk({ client });const result = await sdk.greeting({ query: { input: { name: 'World' } } });const user = await sdk.user.create({ body: { name: 'Bob', age: 30 } });
트랜스포머 사용 시 (superjson, devalue 등)
백엔드가 superjson과 같은 데이터 트랜스포머를 사용하는 경우, 이를 클라이언트 설정에 반드시 전달해야 합니다. 그렇지 않으면 Date, Map, Set 및 기타 JSON이 아닌 타입이 조용히 잘못된 값으로 처리될 수 있습니다.
먼저 Hey API의 프로그래매틱 API를 사용하여 클라이언트 코드를 생성하면 createTRPCHeyApiTypeResolvers를 통해 생성된 타입이 올바른지 확인할 수 있습니다:
src/client.tstsimport { createClient } from '@hey-api/openapi-ts';import { createTRPCHeyApiTypeResolvers } from '@trpc/openapi/heyapi';const openApiJson = './path/to/openapi.json'const outputDir = './generated'await createClient({input: openApiJson,output: outputDir,plugins: [{name: '@hey-api/typescript',// Important: this ensures that your emitted types like Dates are correct'~resolvers': createTRPCHeyApiTypeResolvers(),},{name: '@hey-api/sdk',operations: { strategy: 'single' },},],});
src/client.tstsimport { createClient } from '@hey-api/openapi-ts';import { createTRPCHeyApiTypeResolvers } from '@trpc/openapi/heyapi';const openApiJson = './path/to/openapi.json'const outputDir = './generated'await createClient({input: openApiJson,output: outputDir,plugins: [{name: '@hey-api/typescript',// Important: this ensures that your emitted types like Dates are correct'~resolvers': createTRPCHeyApiTypeResolvers(),},{name: '@hey-api/sdk',operations: { strategy: 'single' },},],});
런타임에서 생성된 클라이언트를 트랜스포머로 구성하면 네이티브 타입을 직접 전달하고 역직렬화된 상태로 반환받을 수 있습니다:
src/usage.tstsimport { configureTRPCHeyApiClient } from '@trpc/openapi/heyapi';import superjson from 'superjson';import { client } from './generated/client.gen';configureTRPCHeyApiClient(client, {baseUrl: 'http://localhost:3000',// Important, this transformer must match your tRPC API's transformer:transformer: superjson,});const sdk = new Sdk({ client });const event = await sdk.getEvent({query: { input: { id: 'evt_1', at: new Date('2025-06-15T10:00:00Z') } },});// event.data.result.data.at is a Date object ✅const created = await sdk.createEvent({body: { name: 'Conference', at: new Date('2025-09-01T09:00:00Z') },});
src/usage.tstsimport { configureTRPCHeyApiClient } from '@trpc/openapi/heyapi';import superjson from 'superjson';import { client } from './generated/client.gen';configureTRPCHeyApiClient(client, {baseUrl: 'http://localhost:3000',// Important, this transformer must match your tRPC API's transformer:transformer: superjson,});const sdk = new Sdk({ client });const event = await sdk.getEvent({query: { input: { id: 'evt_1', at: new Date('2025-06-15T10:00:00Z') } },});// event.data.result.data.at is a Date object ✅const created = await sdk.createEvent({body: { name: 'Conference', at: new Date('2025-09-01T09:00:00Z') },});
다른 생성기 또는 언어 사용
생성된 OpenAPI 스펙은 다음 기능을 지원하는 모든 OpenAPI 호환 클라이언트 생성기와 함께 사용할 수 있습니다:
- Date와 같은 클래스에 대해 정확한 타입 출력
- Search Params 및 요청/응답 바디 직렬화 커스터마이징 지원
tRPC 프로토콜과 올바르게 통합하려면 생성된 클라이언트에 다음 두 가지를 설정해야 합니다:
- 트랜스포머 — tRPC API가 트랜스포머를 사용하는 경우, 클라이언트는 동일한 형식을 사용하여 입력을 직렬화하고 출력을 역직렬화해야 합니다
- 쿼리 입력 — GET 요청은 개별 쿼리 파라미터가 아닌
?input=<JSON>로 입력을 인코딩합니다
완전한 참조 구현은 Hey API 설정 소스를 확인하세요.
트랜스포머
tRPC 데이터 트랜스포머를 사용하면 Date, Map, Set, BigInt와 같은 풍부한 타입을 네트워크를 통해 전송할 수 있습니다. OpenAPI 클라이언트를 사용할 때 입력이 올바르게 직렬화되고 출력이 올바르게 역직렬화되도록 서버와 클라이언트 양쪽에 동일한 트랜스포머를 구성해야 합니다.
tRPC DataTransformer 인터페이스(serialize / deserialize)를 구현하는 모든 트랜스포머가 configureTRPCHeyApiClient와 함께 작동합니다. 아래는 테스트된 옵션들입니다.
SuperJSON
TypeScript-to-TypeScript 환경에서 가장 인기 있는 트랜스포머입니다. Date, Map, Set, BigInt, RegExp 및 기타 많은 타입을 처리합니다.
bashpnpm add superjson
bashpnpm add superjson
src/server.tstsimport { initTRPC } from '@trpc/server';import superjson from 'superjson';const t = initTRPC.create({ transformer: superjson });
src/server.tstsimport { initTRPC } from '@trpc/server';import superjson from 'superjson';const t = initTRPC.create({ transformer: superjson });
src/client.tstsimport { configureTRPCHeyApiClient } from '@trpc/openapi/heyapi';import superjson from 'superjson';import { client } from './generated/client.gen';configureTRPCHeyApiClient(client, {baseUrl: 'http://localhost:3000',transformer: superjson,});
src/client.tstsimport { configureTRPCHeyApiClient } from '@trpc/openapi/heyapi';import superjson from 'superjson';import { client } from './generated/client.gen';configureTRPCHeyApiClient(client, {baseUrl: 'http://localhost:3000',transformer: superjson,});
완전한 엔드투엔드 예제는 superjson 테스트를 확인하세요.
MongoDB Extended JSON v2
EJSON은 크로스 언어 지원이 필요한 경우 좋은 선택입니다. bson npm 패키지는 tRPC DataTransformer에 직접 매핑되는 EJSON.serialize / EJSON.deserialize를 제공합니다.
지원 언어: C, C#, C++, Go, Java, Node.js, Perl, PHP, Python, Ruby, Scala
bashpnpm add bson
bashpnpm add bson
src/transformer.tstsimport { EJSON } from 'bson';import type { TRPCDataTransformer } from '@trpc/server';export const ejsonTransformer: TRPCDataTransformer = {serialize: (value) => EJSON.serialize(value),deserialize: (value) => EJSON.deserialize(value as Document),};
src/transformer.tstsimport { EJSON } from 'bson';import type { TRPCDataTransformer } from '@trpc/server';export const ejsonTransformer: TRPCDataTransformer = {serialize: (value) => EJSON.serialize(value),deserialize: (value) => EJSON.deserialize(value as Document),};
src/client.tstsimport { configureTRPCHeyApiClient } from '@trpc/openapi/heyapi';import { client } from './generated/client.gen';import { ejsonTransformer } from './transformer';configureTRPCHeyApiClient(client, {baseUrl: 'http://localhost:3000',transformer: ejsonTransformer,});
src/client.tstsimport { configureTRPCHeyApiClient } from '@trpc/openapi/heyapi';import { client } from './generated/client.gen';import { ejsonTransformer } from './transformer';configureTRPCHeyApiClient(client, {baseUrl: 'http://localhost:3000',transformer: ejsonTransformer,});
완전한 엔드투엔드 예제는 MongoDB EJSON 테스트를 확인하세요.
Amazon Ion
Amazon Ion은 광범위한 언어 지원을 갖춘 풍부한 타입의 데이터 형식입니다. TRPCDataTransformer 인터페이스를 직접 지원하지 않으며 JS/TS에서 tRPC와 함께 작동하려면 약간의 보일러플레이트가 필요하지만, 자체 시스템에 좋은 선택이 될 수 있습니다.
지원 언어: C, C#, D, Go, Java, JavaScript, PHP, Python, Rust
bashpnpm add ion-js
bashpnpm add ion-js
트랜스포머 구현, 보일러플레이트 및 완전한 엔드투엔드 예제는 Amazon Ion 테스트를 확인하세요.
커스텀 트랜스포머 작성
serialize와 deserialize 메서드를 가진 모든 객체가 작동합니다:
tsimport type { TRPCDataTransformer } from '@trpc/server';const myTransformer: TRPCDataTransformer = {serialize: (value) => {/* encode rich types */},deserialize: (value) => {/* decode them back */},};
tsimport type { TRPCDataTransformer } from '@trpc/server';const myTransformer: TRPCDataTransformer = {serialize: (value) => {/* encode rich types */},deserialize: (value) => {/* decode them back */},};
이를 서버의 initTRPC.create({ transformer })와 클라이언트의 configureTRPCHeyApiClient(client, { transformer })에 모두 전달하세요. 자세한 내용은 데이터 트랜스포머 문서를 확인하세요.
전체 예제
이 모든 단계를 하나로 묶은 완전하고 실행 가능한 프로젝트는 openapi-codegen 예제를 확인하세요.
oasdiff를 사용한 API 변경 로그 및 비호환 변경 검사
OpenAPI 스펙을 생성한 후, oasdiff를 사용하여 두 버전을 비교할 수 있습니다.
설치 옵션(Homebrew, Docker, 바이너리 등)에 대한 자세한 내용은 공식 문서를 확인하세요:
이를 사용하면 변경 로그를 빠르게 생성하고 의도하지 않은 API 비호환 변경을 감지하여 릴리스를 계획하고 조정할 수 있습니다.
sh# Get a complete changelog including minor and breaking changes$ oasdiff changelog packages/openapi/test/routers/superjsonRouter.openapi.json /tmp/superjsonRouter.openapi.next.json3 changes: 2 error, 0 warning, 1 infoerror [new-required-request-property] in API POST /createEventadded the new required request property 'location'error [api-path-removed-without-deprecation] in API GET /getBigIntapi path removed without deprecationinfo [endpoint-added] in API GET /healthendpoint added# Get a list of breaking changes if any$ oasdiff breaking packages/openapi/test/routers/superjsonRouter.openapi.json /tmp/superjsonRouter.openapi.next.json2 changes: 2 error, 0 warning, 0 infoerror [new-required-request-property] in API POST /createEventadded the new required request property 'location'error [api-path-removed-without-deprecation] in API GET /getBigIntapi path removed without deprecation
sh# Get a complete changelog including minor and breaking changes$ oasdiff changelog packages/openapi/test/routers/superjsonRouter.openapi.json /tmp/superjsonRouter.openapi.next.json3 changes: 2 error, 0 warning, 1 infoerror [new-required-request-property] in API POST /createEventadded the new required request property 'location'error [api-path-removed-without-deprecation] in API GET /getBigIntapi path removed without deprecationinfo [endpoint-added] in API GET /healthendpoint added# Get a list of breaking changes if any$ oasdiff breaking packages/openapi/test/routers/superjsonRouter.openapi.json /tmp/superjsonRouter.openapi.next.json2 changes: 2 error, 0 warning, 0 infoerror [new-required-request-property] in API POST /createEventadded the new required request property 'location'error [api-path-removed-without-deprecation] in API GET /getBigIntapi path removed without deprecation