tRPC
tRPC는 엔드투엔드 타입 안전성을 갖춘 TypeScript API를 구축하기 위한 프레임워크입니다. tRPC를 사용하면 API 작업 입력 및 출력에 대한 업데이트가 프로젝트를 다시 빌드할 필요 없이 클라이언트 코드에 즉시 반영되고 IDE에서 확인할 수 있습니다.
tRPC API 생성기는 AWS CDK 또는 Terraform 인프라 설정과 함께 새로운 tRPC API를 생성합니다. 생성된 백엔드는 서버리스 배포를 위해 AWS Lambda를 사용하고, AWS API Gateway API를 통해 노출되며, Zod를 사용한 스키마 검증을 포함합니다. 로깅, AWS X-Ray 추적 및 Cloudwatch Metrics를 포함한 관찰성을 위해 AWS Lambda Powertools를 설정합니다.
사용법
섹션 제목: “사용법”tRPC API 생성
섹션 제목: “tRPC API 생성”두 가지 방법으로 새로운 tRPC API를 생성할 수 있습니다:
이 제너레이터 실행@aws/nx-plugin:ts#api
pnpm nx g @aws/nx-plugin:ts#api yarn nx g @aws/nx-plugin:ts#api npx nx g @aws/nx-plugin:ts#api bunx nx g @aws/nx-plugin:ts#api- 설치 Nx Console VSCode Plugin 아직 설치하지 않았다면
- VSCode에서 Nx 콘솔 열기
- 클릭
Generate (UI)"Common Nx Commands" 섹션에서 - 검색
@aws/nx-plugin - ts#api - 필수 매개변수 입력
- 클릭
Generate
명령 구성하기9
필수
name필수stringAPI의 이름 (필수). 클래스 이름과 파일 경로를 생성하는 데 사용됩니다.
frameworkenum기본값:trpc사용할 API 프레임워크.
trpcsmithyintegrationPatternenum기본값:isolatedAPI에 대한 API Gateway 통합이 생성되는 방식입니다. isolated (기본값) 또는 shared 중에서 선택하세요.
isolatedsharedauthenum기본값:iamAPI 인증에 사용할 방법입니다. iam(기본값), cognito 또는 custom 중에서 선택하세요.
iamcognitocustomdirectorystring기본값:packages애플리케이션을 저장할 디렉토리입니다.
iacenum기본값:inherit선호하는 IaC 공급자입니다. 기본적으로 초기 선택에서 상속됩니다.
inheritcdkterraforminfraenum기본값:rest-lambda이 API를 배포하는 데 사용할 인프라 유형입니다.
rest-lambdahttp-lambdanonesubDirectorystring프로젝트가 배치되는 하위 디렉토리입니다. 기본값은 프로젝트 이름입니다.
preferInstallDependenciesboolean기본값:true생성기 실행 후 의존성 설치를 선호할지 여부입니다. 여러 생성기를 일괄 처리할 때 설치를 연기하려면 false로 설정하세요(후속 생성기가 Nx 프로젝트 그래프를 계산할 수 있도록 필요한 경우 설치는 여전히 실행됩니다). 마지막에 한 번만 설치하세요.
생성기 출력
섹션 제목: “생성기 출력”생성기는 <directory>/<api-name> 디렉토리에 다음과 같은 프로젝트 구조를 생성합니다:
디렉터리src
- index.ts Package entrypoint re-exporting the router, context, client and schema
- init.ts Backend tRPC initialisation
- handler.ts Lambda handler entrypoint
- router.ts tRPC router definition
디렉터리schema Schema definitions using Zod
- index.ts Barrel re-exporting every schema
- echo.ts Example definitions for the input and output of the “echo” procedure
- z-async-iterable.ts Zod helper for subscriptions (REST API only)
디렉터리procedures Procedures (or operations) exposed by your API
- echo.ts Example procedure
디렉터리middleware
- index.ts Barrel re-exporting the middleware, and the procedure context type
- error.ts Middleware for error handling
- logger.ts middleware for configuring AWS Powertools for Lambda logging
- tracer.ts middleware for configuring AWS Powertools for Lambda tracing
- metrics.ts middleware for configuring AWS Powertools for Lambda metrics
- local-server.ts tRPC standalone adapter entrypoint for local development server
디렉터리client
- index.ts Type-safe client for machine-to-machine API calls
- rolldown.config.ts Bundle configuration for the Lambda deployment package
- tsconfig.json TypeScript configuration
- tsconfig.lib.json TypeScript configuration for the library sources
- tsconfig.spec.json TypeScript configuration for the tests
- vitest.config.mts Vitest configuration
- package.json Project manifest defining the project’s package name and dependencies
- project.json Project configuration and build targets
- README.md Project readme
- .gitignore Ignores the project’s build output
인프라
섹션 제목: “인프라”이 생성기는 선택한 iac를 기반으로 코드형 인프라를 제공하므로, 관련 CDK constructs 또는 Terraform 모듈을 포함하는 packages/common에 프로젝트를 생성합니다.
공통 코드형 인프라 프로젝트는 다음과 같이 구성됩니다:
디렉터리packages/common/constructs
디렉터리src
디렉터리app/ 프로젝트/생성기에 특정한 인프라를 위한 Constructs
- …
디렉터리core/
app의 constructs에서 재사용되는 일반 constructs- …
- index.ts
app에서 constructs를 내보내는 진입점
- project.json 프로젝트 빌드 타겟 및 구성
디렉터리packages/common/terraform
디렉터리src
디렉터리app/ 프로젝트/생성기에 특정한 인프라를 위한 Terraform 모듈
- …
디렉터리core/
app의 모듈에서 재사용되는 일반 모듈- …
- project.json 프로젝트 빌드 타겟 및 구성
API를 배포하기 위해 다음 파일들이 생성됩니다:
디렉터리packages/common/constructs/src
디렉터리app
디렉터리apis
- <project-name>.ts CDK construct for deploying your API
디렉터리core
디렉터리api
- http-api.ts CDK construct for deploying an HTTP API (if you selected to deploy an HTTP API)
- rest-api.ts CDK construct for deploying a REST API (if you selected to deploy a REST API)
- utils.ts Utilities for the API constructs
디렉터리packages/common/terraform/src
디렉터리app
디렉터리apis
디렉터리<project-name>
- <project-name>.tf Module for deploying your API
디렉터리core
디렉터리api
디렉터리http-api
- http-api.tf Module for deploying an HTTP API (if you selected to deploy an HTTP API)
디렉터리rest-api
- rest-api.tf Module for deploying a REST API (if you selected to deploy a REST API)
아키텍처
섹션 제목: “아키텍처”배포된 애플리케이션은 다음과 같은 아키텍처를 가집니다: 핸들러를 실행하는 Lambda 함수 앞에 API Gateway API가 있습니다.
REST API는 API Gateway 스테이지 앞에 AWS 관리형 기본 규칙 세트가 활성화된 AWS WAFv2 Web ACL을 포함합니다.
HTTP API는 WAF를 직접 지원하지 않습니다 — WAF 보호가 필요한 경우 REST API를 선택하거나 HTTP API 앞에 CloudFront 배포를 배치하세요.
tRPC API 구현
섹션 제목: “tRPC API 구현”높은 수준에서 tRPC API는 특정 프로시저에 요청을 위임하는 라우터로 구성됩니다. 각 프로시저는 Zod 스키마로 정의된 입력과 출력을 가집니다.
스키마
섹션 제목: “스키마”src/schema 디렉토리에는 클라이언트와 서버 코드 간에 공유되는 타입이 포함되어 있습니다. 이 패키지에서 이러한 타입은 TypeScript 우선 스키마 선언 및 검증 라이브러리인 Zod를 사용하여 정의됩니다.
예제 스키마는 다음과 같을 수 있습니다:
import { z } from 'zod';
// Schema definitionexport const UserSchema = z.object({ name: z.string(), height: z.number(), dateOfBirth: z.string().datetime(),});
// Corresponding TypeScript typeexport type User = z.TypeOf<typeof UserSchema>;위 스키마가 주어지면 User 타입은 다음 TypeScript와 동일합니다:
interface User { name: string; height: number; dateOfBirth: string;}스키마는 서버와 클라이언트 코드 모두에서 공유되므로 API에서 사용되는 구조를 변경할 때 업데이트할 단일 위치를 제공합니다.
스키마는 tRPC API에 의해 런타임에 자동으로 검증되므로 백엔드에서 사용자 정의 검증 로직을 수작업으로 작성할 필요가 없습니다.
Zod는 .merge, .pick, .omit 등과 같이 스키마를 결합하거나 파생하는 강력한 유틸리티를 제공합니다. 자세한 내용은 Zod 문서 웹사이트에서 확인할 수 있습니다.
라우터 및 프로시저
섹션 제목: “라우터 및 프로시저”tRPC 라우터는 src/router.ts에 정의되어 있으며 모든 프로시저를 등록합니다. 각 프로시저는 예상되는 입력, 출력 및 구현을 정의합니다. Lambda 핸들러 진입점은 src/handler.ts에 있으며 요청을 라우터로 전달합니다.
생성된 샘플 라우터에는 echo라는 단일 작업이 있습니다:
import { echo } from './procedures/echo.js';
export const appRouter = router({ echo,});예제 echo 프로시저는 src/procedures/echo.ts에 생성됩니다:
export const echo = publicProcedure .input(EchoInputSchema) .output(EchoOutputSchema) .query((opts) => ({ message: opts.input.message }));위 내용을 분석하면:
publicProcedure는src/middleware에 설정된 미들웨어를 포함하여 API의 공개 메서드를 정의합니다. 이 미들웨어에는 로깅, 추적 및 메트릭을 위한 AWS Lambda Powertools 통합이 포함됩니다.input은 작업에 대한 예상 입력을 정의하는 Zod 스키마를 받습니다. 이 작업에 대해 전송된 요청은 이 스키마에 대해 자동으로 검증됩니다.output은 작업에 대한 예상 출력을 정의하는 Zod 스키마를 받습니다. 스키마에 맞지 않는 출력을 반환하면 구현에서 타입 오류가 표시됩니다.query는 API의 구현을 정의하는 함수를 받습니다. 이 구현은opts를 받으며, 여기에는 작업에 전달된input과 미들웨어에 의해 설정된 다른 컨텍스트(opts.ctx에서 사용 가능)가 포함됩니다.query에 전달된 함수는output스키마에 맞는 출력을 반환해야 합니다.
구현을 정의하기 위해 query를 사용하는 것은 작업이 변경을 일으키지 않음을 나타냅니다. 데이터를 검색하는 메서드를 정의하는 데 사용하세요. 변경을 일으키는 작업을 구현하려면 대신 mutation 메서드를 사용하세요.
새 프로시저를 추가하는 경우 src/router.ts의 라우터에 추가하여 등록해야 합니다.
구독 (스트리밍)
섹션 제목: “구독 (스트리밍)”tRPC 구독을 사용하면 Server-Sent Events (SSE)를 사용하여 서버에서 클라이언트로 데이터를 스트리밍할 수 있습니다. 컴퓨팅 유형으로 rest-lambda를 선택하면 생성기가 스트리밍에 필요한 인프라와 스트리밍 Lambda 핸들러 및 ZodAsyncIterable 스키마 헬퍼를 자동으로 구성합니다.
구독 프로시저를 정의하려면 비동기 생성기 함수와 함께 .subscription 메서드를 사용하세요. src/schema/z-async-iterable.ts의 ZodAsyncIterable 헬퍼를 사용하여 출력 스키마를 정의하세요:
import { publicProcedure } from '../init.js';import { z } from 'zod';import { ZodAsyncIterable } from '../schema/z-async-iterable.js';
const InputSchema = z.object({ query: z.string() });const ChunkSchema = z.object({ text: z.string() });
export const myStream = publicProcedure .input(InputSchema) .output( ZodAsyncIterable({ yield: ChunkSchema, }), ) .subscription(async function* (opts) { // Yield data to the client as it becomes available for (const chunk of await getResults(opts.input.query)) { yield { text: chunk }; } });다른 프로시저와 마찬가지로 라우터에 구독을 등록하세요:
export const appRouter = router({ echo, myStream,});생성된 인프라는 모든 REST API 작업에 대해 API Gateway에서 ResponseTransferMode.STREAM을 사용하는 스트리밍 Lambda 핸들러를 사용하므로 구독이 일반 쿼리 및 뮤테이션과 함께 작동할 수 있습니다.
tRPC API 사용자 정의
섹션 제목: “tRPC API 사용자 정의”구현에서 TRPCError를 throw하여 클라이언트에 오류 응답을 반환할 수 있습니다. 이는 오류 유형을 나타내는 code를 받습니다. 예를 들어:
throw new TRPCError({ code: 'NOT_FOUND', message: 'The requested resource could not be found',});작업 구성
섹션 제목: “작업 구성”API가 성장함에 따라 관련 작업을 함께 그룹화하고 싶을 수 있습니다.
중첩된 라우터를 사용하여 작업을 함께 그룹화할 수 있습니다. 예를 들어:
import { getUser } from './procedures/users/get.js';import { listUsers } from './procedures/users/list.js';
const appRouter = router({ users: router({ get: getUser, list: listUsers, }), ...})클라이언트는 이 작업 그룹화를 받습니다. 예를 들어 이 경우 listUsers 작업을 호출하는 것은 다음과 같을 수 있습니다:
client.users.list.query();AWS Lambda Powertools 로거는 src/middleware/logger.ts에 구성되어 있으며 opts.ctx.logger를 통해 API 구현에서 액세스할 수 있습니다. 이를 사용하여 CloudWatch Logs에 로그를 기록하거나 모든 구조화된 로그 메시지에 포함할 추가 값을 제어할 수 있습니다. 예를 들어:
export const echo = publicProcedure .input(...) .output(...) .query(async (opts) => { opts.ctx.logger.info('Operation called with input', opts.input);
return ...; });로거에 대한 자세한 내용은 AWS Lambda Powertools Logger 문서를 참조하세요.
메트릭 기록
섹션 제목: “메트릭 기록”AWS Lambda Powertools 메트릭은 src/middleware/metrics.ts에 구성되어 있으며 opts.ctx.metrics를 통해 API 구현에서 액세스할 수 있습니다. 이를 사용하여 AWS SDK를 가져오고 사용할 필요 없이 CloudWatch에 메트릭을 기록할 수 있습니다. 예를 들어:
export const echo = publicProcedure .input(...) .output(...) .query(async (opts) => { opts.ctx.metrics.addMetric('Invocations', 'Count', 1);
return ...; });자세한 내용은 AWS Lambda Powertools Metrics 문서를 참조하세요.
X-Ray 추적 미세 조정
섹션 제목: “X-Ray 추적 미세 조정”AWS Lambda Powertools 추적기는 src/middleware/tracer.ts에 구성되어 있으며 opts.ctx.tracer를 통해 API 구현에서 액세스할 수 있습니다. 이를 사용하여 AWS X-Ray로 추적을 추가하여 API 요청의 성능과 흐름에 대한 자세한 인사이트를 제공할 수 있습니다. 예를 들어:
export const echo = publicProcedure .input(...) .output(...) .query(async (opts) => { const subSegment = opts.ctx.tracer.getSegment()!.addNewSubsegment('MyAlgorithm'); // ... my algorithm logic to capture subSegment.close();
return ...; });자세한 내용은 AWS Lambda Powertools Tracer 문서를 참조하세요.
사용자 정의 미들웨어 구현
섹션 제목: “사용자 정의 미들웨어 구현”미들웨어를 구현하여 프로시저에 제공되는 컨텍스트에 추가 값을 추가할 수 있습니다.
예를 들어, src/middleware/identity.ts에서 API를 호출하는 사용자에 대한 세부 정보를 추출하는 미들웨어를 구현해 보겠습니다.
이 예제는 IAM 인증을 위한 ID 미들웨어를 안내합니다. API Gateway 이벤트에서 추출한 sub를 사용하여 Cognito에서 호출자를 조회합니다.
조회는 생성된 tRPC API의 종속성이 아닌 Cognito Identity Provider 클라이언트를 사용합니다. 먼저 API 프로젝트에 설치하세요:
pnpm add @aws-sdk/client-cognito-identity-provider@3.1126.0 --filter my-apiyarn workspace @my-scope/my-api add @aws-sdk/client-cognito-identity-provider@3.1126.0npm install --legacy-peer-deps @aws-sdk/client-cognito-identity-provider@3.1126.0 -w packages/my-apibun add @aws-sdk/client-cognito-identity-provider@3.1126.0 --cwd packages/my-api먼저 컨텍스트에 추가할 내용을 정의합니다:
export interface IIdentityContext { identity?: { sub: string; username: string; };}컨텍스트에 추가 선택적 속성을 정의합니다. tRPC는 이 미들웨어를 올바르게 구성한 프로시저에서 이것이 정의되도록 관리합니다.
다음으로 미들웨어 자체를 구현합니다. 다음과 같은 구조를 가집니다:
export const createIdentityPlugin = () => { const t = initTRPC.context<...>().create(); return t.procedure.use(async (opts) => { // Add logic here to run before the procedure
const response = await opts.next(...);
// Add logic here to run after the procedure
return response; });};우리의 경우 호출하는 Cognito 사용자에 대한 세부 정보를 추출하려고 합니다. API Gateway 이벤트에서 사용자의 주체 ID(또는 “sub”)를 추출하고 Cognito에서 사용자 세부 정보를 검색하여 이를 수행합니다. 구현은 REST API 또는 HTTP API에 의해 함수에 이벤트가 제공되었는지에 따라 다릅니다:
import { CognitoIdentityProvider } from '@aws-sdk/client-cognito-identity-provider';import { initTRPC, TRPCError } from '@trpc/server';import type { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';import type { APIGatewayProxyEvent } from 'aws-lambda';
export interface IIdentityContext { identity?: { sub: string; username: string; };}
export const createIdentityPlugin = () => { const t = initTRPC .context< IIdentityContext & CreateAWSLambdaContextOptions<APIGatewayProxyEvent> >() .create();
const cognito = new CognitoIdentityProvider();
return t.procedure.use(async (opts) => { const cognitoAuthenticationProvider = opts.ctx.event.requestContext?.identity?.cognitoAuthenticationProvider;
let sub: string | undefined = undefined; if (cognitoAuthenticationProvider) { const providerParts = cognitoAuthenticationProvider.split(':'); sub = providerParts[providerParts.length - 1]; }
if (!sub) { throw new TRPCError({ code: 'FORBIDDEN', message: `Unable to determine calling user`, }); }
const { Users } = await cognito.listUsers({ // Assumes user pool id is configured in lambda environment UserPoolId: process.env.USER_POOL_ID!, Limit: 1, Filter: `sub="${sub}"`, });
if (!Users || Users.length !== 1) { throw new TRPCError({ code: 'FORBIDDEN', message: `No user found with subjectId ${sub}`, }); }
// Provide the identity to other procedures in the context return await opts.next({ ctx: { ...opts.ctx, identity: { sub, username: Users[0].Username!, }, }, }); });};import { CognitoIdentityProvider } from '@aws-sdk/client-cognito-identity-provider';import { initTRPC, TRPCError } from '@trpc/server';import type { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';import type { APIGatewayProxyEventV2WithIAMAuthorizer } from 'aws-lambda';
export interface IIdentityContext { identity?: { sub: string; username: string; };}
export const createIdentityPlugin = () => { const t = initTRPC .context< IIdentityContext & CreateAWSLambdaContextOptions<APIGatewayProxyEventV2WithIAMAuthorizer> >() .create();
const cognito = new CognitoIdentityProvider();
return t.procedure.use(async (opts) => { const cognitoIdentity = opts.ctx.event.requestContext?.authorizer?.iam ?.cognitoIdentity as unknown as | { amr: string[]; } | undefined;
const sub = (cognitoIdentity?.amr ?? []) .flatMap((s) => (s.includes(':CognitoSignIn:') ? [s] : [])) .map((s) => { const parts = s.split(':'); return parts[parts.length - 1]; })?.[0];
if (!sub) { throw new TRPCError({ code: 'FORBIDDEN', message: `Unable to determine calling user`, }); }
const { Users } = await cognito.listUsers({ // Assumes user pool id is configured in lambda environment UserPoolId: process.env.USER_POOL_ID!, Limit: 1, Filter: `sub="${sub}"`, });
if (!Users || Users.length !== 1) { throw new TRPCError({ code: 'FORBIDDEN', message: `No user found with subjectId ${sub}`, }); }
// Provide the identity to other procedures in the context return await opts.next({ ctx: { ...opts.ctx, identity: { sub, username: Users[0].Username!, }, }, }); });};auth: 'cognito'로 배포하면 API Gateway Cognito 권한 부여자가 호출자가 Authorization 헤더에 제공하는 JWT를 확인하고 확인된 클레임을 Lambda 이벤트에 배치합니다. 우리의 미들웨어는 이러한 클레임을 읽기만 하면 됩니다 — 추가 AWS SDK 호출이나 수동 JWT 확인이 필요하지 않습니다.
먼저 컨텍스트에 추가할 내용을 정의합니다:
export interface IIdentityContext { identity?: { sub: string; username: string; };}컨텍스트에 추가 선택적 속성을 정의합니다. tRPC는 이 미들웨어를 올바르게 구성한 프로시저에서 이것이 정의되도록 관리합니다.
다음으로 미들웨어 자체입니다. 이벤트 타입과 클레임의 위치는 REST API와 HTTP API 간에 다르므로 구현은 선택한 infra에 따라 달라집니다:
REST API의 Cognito User Pools 권한 부여자는 클레임을 event.requestContext.authorizer.claims에 배치합니다:
import { initTRPC, TRPCError } from '@trpc/server';import type { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';import type { APIGatewayProxyEvent } from 'aws-lambda';
export interface IIdentityContext { identity?: { sub: string; username: string; };}
export const createIdentityPlugin = () => { const t = initTRPC .context< IIdentityContext & CreateAWSLambdaContextOptions<APIGatewayProxyEvent> >() .create();
return t.procedure.use(async (opts) => { const claims = opts.ctx.event.requestContext?.authorizer?.claims as | Record<string, string> | undefined;
const sub = claims?.sub; const username = claims?.username ?? claims?.['cognito:username'];
if (!sub || !username) { throw new TRPCError({ code: 'FORBIDDEN', message: 'Unable to determine calling user', }); }
return await opts.next({ ctx: { ...opts.ctx, identity: { sub, username, }, }, }); });};HTTP API의 JWT 권한 부여자는 클레임이 한 단계 더 깊은 event.requestContext.authorizer.jwt.claims에 있는 payload-v2 이벤트를 전달합니다. 컨텍스트는 생성된 publicProcedure가 사용하는 것과 일치하도록 APIGatewayProxyEventV2WithJWTAuthorizer로 타입이 지정되어야 합니다 — 그렇지 않으면 .concat()이 tRPC의 Context mismatch 오류와 함께 실패합니다:
import { initTRPC, TRPCError } from '@trpc/server';import type { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';import type { APIGatewayProxyEventV2WithJWTAuthorizer } from 'aws-lambda';
export interface IIdentityContext { identity?: { sub: string; username: string; };}
export const createIdentityPlugin = () => { const t = initTRPC .context< IIdentityContext & CreateAWSLambdaContextOptions<APIGatewayProxyEventV2WithJWTAuthorizer> >() .create();
return t.procedure.use(async (opts) => { const claims = opts.ctx.event.requestContext?.authorizer?.jwt?.claims as | Record<string, string> | undefined;
const sub = claims?.sub; const username = claims?.username ?? claims?.['cognito:username'];
if (!sub || !username) { throw new TRPCError({ code: 'FORBIDDEN', message: 'Unable to determine calling user', }); }
return await opts.next({ ctx: { ...opts.ctx, identity: { sub, username, }, }, }); });};그런 다음 호출자의 ID가 필요한 모든 프로시저에 플러그인을 혼합할 수 있습니다:
import { publicProcedure } from '../init.js';import { createIdentityPlugin } from '../middleware/identity.js';import { z } from 'zod';
export const me = publicProcedure .concat(createIdentityPlugin()) .output(z.object({ sub: z.string(), username: z.string() })) .query(({ ctx }) => ({ sub: ctx.identity!.sub, username: ctx.identity!.username, }));tRPC API 배포
섹션 제목: “tRPC API 배포”tRPC API 생성기는 선택한 iac를 기반으로 CDK 또는 Terraform 인프라 코드를 생성합니다. 이를 사용하여 tRPC API를 배포할 수 있습니다.
API를 배포하기 위한 CDK 구성은 common/constructs 폴더에 있습니다. 이를 CDK 애플리케이션에서 사용할 수 있습니다. 예를 들어:
import { MyApi } from '@my-scope/common-constructs';
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { // Add the api to your stack const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), }); }}import { MyApi, UserIdentity } from '@my-scope/common-constructs';
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { // Add the api to your stack const identity = new UserIdentity(this, 'Identity');
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), identity, }); }}UserIdentity 구성은 ts#website#auth 생성기를 사용하여 생성할 수 있습니다.
이는 선택한 auth 방법을 기반으로 AWS API Gateway REST 또는 HTTP API, 비즈니스 로직을 위한 AWS Lambda 함수 및 인증을 포함한 API 인프라를 설정합니다.
API를 배포하기 위한 Terraform 모듈은 common/terraform 폴더에 있습니다. 이를 Terraform 구성에서 사용할 수 있습니다.
API 모듈은 공유 S3 자산 버킷에 Lambda 배포 zip을 스테이징합니다 — 자세한 내용은 Terraform 인프라 가이드를 참조하세요. 배포당 한 번 core/asset-bucket 모듈을 인스턴스화하고 asset_bucket_name 입력을 통해 모든 API / Lambda 모듈에 bucket_name 출력을 전달하세요:
module "asset_bucket" { source = "../../common/terraform/src/core/asset-bucket"}
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# Environment variables for the Lambda function env = { ENVIRONMENT = var.environment LOG_LEVEL = "INFO" }
# Additional IAM policies if needed additional_iam_policy_statements = [ # Add any additional permissions your API needs ]
tags = local.common_tags}module "asset_bucket" { source = "../../common/terraform/src/core/asset-bucket"}
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
user_pool_id = local.user_pool_id user_pool_client_ids = [local.client_id]
# Environment variables for the Lambda function env = { ENVIRONMENT = var.environment LOG_LEVEL = "INFO" }
# Additional IAM policies if needed additional_iam_policy_statements = [ # Add any additional permissions your API needs ]
tags = local.common_tags}적절한 Terraform 리소스 또는 모듈을 사용하여 Cognito User Pool 및 Client를 설정할 수 있습니다.
이는 다음을 설정합니다:
- 모든 tRPC 프로시저를 제공하는 AWS Lambda 함수
- 함수 트리거로서의 API Gateway HTTP/REST API
- IAM 역할 및 권한
- CloudWatch 로그 그룹
- X-Ray 추적 구성
- CORS 구성
Terraform 모듈은 사용할 수 있는 여러 출력을 제공합니다:
# Access the API endpointoutput "api_url" { value = module.my_api.stage_invoke_url}
# Access Lambda function detailsoutput "lambda_function_name" { value = module.my_api.lambda_function_name}
# Access IAM role for granting additional permissionsoutput "lambda_execution_role_arn" { value = module.my_api.lambda_execution_role_arn}모듈에 변수를 전달하여 CORS 설정을 사용자 정의할 수 있습니다:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# Custom CORS configuration cors_allow_origins = ["https://myapp.com", "https://staging.myapp.com"] cors_allow_methods = ["GET", "POST", "PUT", "DELETE"] cors_allow_headers = [ "authorization", "content-type", "x-custom-header" ]
tags = local.common_tags}WAF
섹션 제목: “WAF”REST API의 경우, 생성된 구성 요소는 기본적으로 AWS WAFv2 Web ACL을 API Gateway 스테이지와 연결합니다. Web ACL은 AWS 관리형 기본 규칙 세트(AWSManagedRulesCommonRuleSet 및 AWSManagedRulesKnownBadInputsRuleSet)를 사용하여 OWASP Top 10을 포함한 일반적인 웹 공격으로부터 보호합니다. WAF 요청 로그는 CloudWatch Logs 그룹에 기록됩니다.
생성된 rest-api 구성 요소를 편집하여 규칙을 추가, 제거 또는 조정할 수 있습니다(예: 속도 기반 규칙 또는 추가 관리형 규칙 그룹 추가).
옵트아웃하려면(예: 자체 Web ACL을 연결하려면) enableWaf를 false로 설정하세요:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), enableWaf: false,});옵트아웃하려면(예: 자체 Web ACL을 연결하려면) enable_waf를 false로 설정하세요:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name enable_waf = false}액세스 로깅
섹션 제목: “액세스 로깅”REST API의 경우, 생성된 인프라는 기본적으로 액세스 로깅을 활성화하여 요청당 하나의 구조화된 JSON 라인을 전용 CloudWatch Logs 그룹에 작성합니다. 로그 그룹은 고객 관리형 KMS 키로 암호화되며 1년 동안 보관됩니다.
API Gateway는 계정 수준의 CloudWatch Logs 역할을 사용하여 액세스 로그를 작성합니다. 이 역할은 AWS::ApiGateway::Account 설정에 구성되며, 이는 리전당 계정당 싱글톤입니다 — 리전의 모든 REST API에 대해 하나의 역할만 존재합니다. 독립적으로 배포된 여러 스택에서 이를 안전하게 관리하기 위해, 생성된 인프라는:
- 작동하는 역할이 이미 설정되어 있지 않은 경우에만 공유 CloudWatch Logs 역할을 생성하고 계정에 구성하므로, 배포가 다른 스택이 소유한 역할을 덮어쓰지 않습니다.
- 해체 시 계정 설정을 그대로 두므로, 하나의 스택을 삭제해도 리전의 다른 REST API에 대한 로깅이 비활성화되지 않습니다.
계정 역할은 ApiGatewayAccount 구성 요소에 의해 관리되며, 이는 ApiGatewayAccount.ensure(scope)를 통해 해결되는 스택 범위 싱글톤입니다. 각 REST API의 스테이지는 이에 의존하며, 역할은 Lambda 기반 사용자 지정 리소스에 의해 구성됩니다.
액세스 로그 형식은 API가 확장하는 RestApi 구성 요소에 의해 설정됩니다. 이를 사용자 지정하려면 생성된 packages/common/constructs/src/app/apis/my-api.ts에서 deployOptions를 super에 전달하되, 구성 요소가 이미 설정한 tracingEnabled를 유지하세요:
super(scope, id, { apiName: 'MyApi', // ... deployOptions: { tracingEnabled: true, accessLogFormat: AccessLogFormat.clf(), }, ...props,});AccessLogFormat은 aws-cdk-lib/aws-apigateway에서 가져옵니다. 설정하지 않은 항목은 구성 요소의 기본값(표준 필드가 포함된 JSON 형식)을 유지합니다.
계정 역할은 생성된 API 모듈에 의해 인스턴스화되는 core/api/api-gateway-account 모듈에 의해 관리됩니다. 이는 계정을 멱등적으로 구성하며 terraform destroy 시 재설정되지 않습니다.
생성된 API 모듈의 aws_api_gateway_stage 리소스에서 access_log_settings 블록을 편집하여 액세스 로그 형식을 사용자 지정할 수 있습니다.
REST/HTTP API CDK 구성은 각 작업에 대한 통합을 정의하기 위한 타입 안전 인터페이스를 제공하도록 구성되어 있습니다.
기본 통합
섹션 제목: “기본 통합”정적 defaultIntegrations를 사용하여 각 작업에 대해 개별 AWS Lambda 함수를 정의하는 기본 패턴을 활용할 수 있습니다:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});생성된 모듈은 API가 생성된 패턴에 대한 기본 통합을 이미 정의하고 있으므로 추가 구성이 필요하지 않습니다:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
tags = local.common_tags}기본 isolated 패턴을 사용하면 작업당 하나의 Lambda 함수가 생성됩니다.
통합 액세스
섹션 제목: “통합 액세스”API 구성의 integrations 속성을 통해 타입 안전 방식으로 기본 AWS Lambda 함수에 액세스할 수 있습니다. 예를 들어, API가 sayHello라는 작업을 정의하고 이 함수에 일부 권한을 추가해야 하는 경우 다음과 같이 할 수 있습니다:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
// sayHello is typed to the operations defined in your APIapi.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({ effect: Effect.ALLOW, actions: [...], resources: [...],}));API가 shared 패턴을 사용하는 경우, 공유 라우터 Lambda는 api.integrations.$router로 노출됩니다:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
api.integrations.$router.handler.addEnvironment('LOG_LEVEL', 'DEBUG');withOverrides를 통해 모든 작업을 재정의하면 기본 라우터 통합을 사용하는 작업이 남아 있지 않으므로 $router를 더 이상 사용할 수 없습니다.
isolated 패턴을 사용하면 모듈의 출력은 작업 이름으로 키가 지정된 맵이므로 단일 작업의 리소스에 접근할 수 있습니다. 예를 들어, 하나의 작업의 Lambda 함수에 추가 권한을 부여하려면:
# Grant additional permissions to just the sayHello operation's functionresource "aws_iam_role_policy" "say_hello_permissions" { name = "say-hello-additional-permissions" role = module.my_api.lambda_execution_role_names["sayHello"]
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "s3:GetObject", "s3:PutObject" ] Resource = "arn:aws:s3:::my-bucket/*" } ] })}모든 작업에 동일한 권한을 부여하려면 operations 출력을 반복합니다:
resource "aws_iam_role_policy" "additional_permissions" { for_each = toset(module.my_api.operations)
name = "additional-api-permissions" role = module.my_api.lambda_execution_role_names[each.key]
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = ["s3:GetObject"] Resource = "arn:aws:s3:::my-bucket/*" } ] })}모듈은 또한 작업 이름으로 키가 지정된 맵으로 lambda_function_names, lambda_function_arns, lambda_invoke_arns, integration_ids 및 lambda_log_group_names를 노출합니다. shared 패턴을 사용하면 함수가 하나만 있으므로 대신 동등한 단수 출력(lambda_execution_role_name, lambda_function_name, …)이 노출됩니다.
모든 작업에 필요한 권한은 모듈에 전달하는 것이 좋으며, 모듈은 각 함수의 역할에 이를 적용합니다:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
additional_iam_policy_statements = [ { Effect = "Allow" Action = ["s3:GetObject"] Resource = ["arn:aws:s3:::my-bucket/*"] } ]}기본 옵션 사용자 정의
섹션 제목: “기본 옵션 사용자 정의”각 기본 통합에 대해 Lambda 함수를 생성할 때 사용되는 옵션을 사용자 정의하려면 withDefaultOptions 메서드를 사용할 수 있습니다. 예를 들어, 모든 Lambda 함수를 Vpc에 배치하려면:
const vpc = new Vpc(this, 'Vpc', ...);
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withDefaultOptions({ vpc, }) .build(),});VPC 구성은 생성된 모듈에서 이미 지원됩니다 — enable_vpc를 vpc_id 및 subnet_ids와 함께 설정하면 모듈이 생성하는 공유 보안 그룹 뒤의 VPC에 모든 Lambda 함수를 배포합니다:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
asset_bucket_name = module.asset_bucket.bucket_name
# VPC configuration enable_vpc = true vpc_id = aws_vpc.main.id subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_b.id]
tags = local.common_tags}모듈이 노출하지 않는 옵션의 경우 생성된 Terraform 모듈의 aws_lambda_function 리소스를 직접 편집하세요. isolated 패턴을 사용하면 해당 단일 리소스가 for_each = local.operations로 선언되므로 거기서 편집하면 모든 작업에 적용됩니다.
작업별 옵션 사용자 정의
섹션 제목: “작업별 옵션 사용자 정의”특정 작업에 대한 기본 통합을 생성하는 데 사용되는 옵션을 사용자 정의하려면(다른 작업에 영향을 주지 않고) withOperationOptions 메서드를 사용할 수 있습니다. 예를 들어, 하나의 작업에 대해서만 Lambda 함수 타임아웃을 늘리려면:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOperationOptions({ sayHello: { timeout: Duration.seconds(60), }, }) .build(),});
// The selected operations remain default integrations, so they're still typed accordingly:api.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({ ... }));지정한 옵션은 기본 통합 옵션(및 withDefaultOptions를 통해 설정된 모든 옵션)과 병합됩니다. withOverrides를 통해 교체한 작업에 대해서는 옵션을 지정할 수 없습니다. 이러한 작업은 더 이상 기본 통합을 사용하지 않기 때문입니다.
호출 순서에 관계없이 withOperationOptions와 withOverrides 모두에서 동일한 작업을 대상으로 하는 경우 타입 오류가 발생합니다.
isolated 패턴을 사용하면 Lambda 함수 리소스가 이미 작업별로 있으므로 작업 이름별로 옵션을 다르게 할 수 있습니다. 예를 들어, 하나의 작업에 더 긴 타임아웃을 부여하려면 생성된 모듈의 aws_lambda_function 리소스를 편집하세요:
resource "aws_lambda_function" "api_lambda" { for_each = local.operations
# Default to 30 seconds, but allow longer for specific operations timeout = lookup({ sayHello = 60 }, each.key, 30)
# ... rest of configuration}통합 재정의
섹션 제목: “통합 재정의”withOverrides 메서드를 사용하여 특정 작업에 대한 통합을 재정의할 수도 있습니다. 각 재정의는 HTTP 또는 REST API에 대한 적절한 CDK 통합 구성으로 타입이 지정된 integration 속성을 지정해야 합니다. withOverrides 메서드도 타입 안전합니다. 예를 들어, getDocumentation API를 일부 외부 웹사이트에서 호스팅하는 문서를 가리키도록 재정의하려면 다음과 같이 할 수 있습니다:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getDocumentation: { integration: new HttpIntegration('https://example.com/documentation'), }, }) .build(),});또한 재정의된 통합은 api.integrations.getDocumentation을 통해 액세스할 때 더 이상 handler 속성을 갖지 않습니다.
통합에 추가 속성을 추가할 수 있으며 이는 타입에 따라 적절하게 지정되어 다른 유형의 통합을 추상화하면서도 타입 안전을 유지할 수 있습니다. 예를 들어 REST API에 대한 S3 통합을 생성했고 나중에 특정 작업에 대한 버킷을 참조하려는 경우 다음과 같이 할 수 있습니다:
const storageBucket = new Bucket(this, 'Bucket', { ... });
const apiGatewayRole = new Role(this, 'ApiGatewayS3Role', { assumedBy: new ServicePrincipal('apigateway.amazonaws.com'),});
storageBucket.grantRead(apiGatewayRole);
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getFile: { bucket: storageBucket, integration: new AwsIntegration({ service: 's3', integrationHttpMethod: 'GET', path: `${storageBucket.bucketName}/{fileName}`, options: { credentialsRole: apiGatewayRole, requestParameters: { 'integration.request.path.fileName': 'method.request.querystring.fileName', }, integrationResponses: [{ statusCode: '200' }], }, }), options: { requestParameters: { 'method.request.querystring.fileName': true, }, methodResponses: [{ statusCode: '200', }], } }, }) .build(),});
// Later, perhaps in another file, you can access the bucket property we defined// in a type-safe mannerapi.integrations.getFile.bucket.grantRead(...);특정 작업을 다른 통합 유형으로 지정하려면 기본 for_each에서 제외하고 통합을 별도로 선언하세요. 예를 들어, 외부 웹사이트에서 getDocumentation을 제공하려면:
# Exclude the overridden operation from the default per-operation resourceslocals { overridden_operations = ["getDocumentation"] default_operations = { for op, details in local.operations : op => details if !contains(local.overridden_operations, op) }}
# Then use local.default_operations in place of local.operations for the# aws_lambda_function, aws_iam_role, aws_apigatewayv2_integration and# aws_lambda_permission resources, and add the override:resource "aws_apigatewayv2_integration" "get_documentation" { api_id = module.http_api.api_id integration_type = "HTTP_PROXY" integration_uri = "https://example.com/documentation" integration_method = "GET"}
resource "aws_apigatewayv2_route" "get_documentation" { api_id = module.http_api.api_id route_key = local.route_key["getDocumentation"] target = "integrations/${aws_apigatewayv2_integration.get_documentation.id}"}인증자 재정의
섹션 제목: “인증자 재정의”통합에 options를 제공하여 인증자와 같은 특정 메서드 옵션을 재정의할 수도 있습니다. 예를 들어 getDocumentation 작업에 Cognito 인증을 사용하려면:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getDocumentation: { integration: new HttpIntegration('https://example.com/documentation'), options: { authorizer: new CognitoUserPoolsAuthorizer(...) // for REST, or HttpUserPoolAuthorizer for an HTTP API } }, }) .build(),});인증은 각 작업에 대해 라우트(HTTP API) 또는 메서드(REST API)에 설정되므로 작업 이름별로 다르게 할 수 있습니다. 예를 들어, HTTP API에서 하나의 작업을 인증되지 않은 상태로 두려면:
resource "aws_apigatewayv2_route" "operation_routes" { for_each = local.operations
# ... rest of configuration
authorization_type = each.key == "getDocumentation" ? "NONE" : "AWS_IAM"}IAM 인증 REST API의 경우 해당 작업의 경로에 대한 인증되지 않은 액세스를 허용하는 리소스 정책 문도 추가하세요.
명시적 통합
섹션 제목: “명시적 통합”원하는 경우 기본 통합을 사용하지 않고 각 작업에 대해 직접 통합을 제공할 수 있습니다. 이는 예를 들어 각 작업이 다른 유형의 통합을 사용해야 하거나 새 작업을 추가할 때 타입 오류를 받고 싶을 때 유용합니다:
new MyApi(this, 'MyApi', { integrations: { sayHello: { integration: new LambdaIntegration(...), }, getDocumentation: { integration: new HttpIntegration(...), }, },});isolated 패턴에서 사용하는 for_each를 각 작업에 대한 Lambda 함수, 통합 및 권한의 명시적 인스턴스화로 교체하세요.
통합 패턴
섹션 제목: “통합 패턴”생성된 API는 두 가지 통합 패턴을 지원합니다:
isolated는 작업당 하나의 Lambda 함수를 생성합니다. 이것이 API의 기본값이며 권장 옵션입니다.shared는 단일 기본 라우터 Lambda를 생성하고 특정 통합을 재정의하지 않는 한 모든 작업에 재사용합니다.
isolated는 작업별로 더 세밀한 권한과 구성을 제공하며 로그 및 추적에 대한 더 나은 분리를 제공합니다. shared는 사용량이 적은 API에서 콜드 스타트가 발생할 가능성을 줄입니다.
통합 패턴은 API 구성을 업데이트하여 CDK에서 언제든지 변경할 수 있습니다. 예를 들어, pattern을 'shared'로 설정하면 통합당 하나가 아닌 단일 함수를 생성합니다:
export class MyApi<...> extends ... {
public static defaultIntegrations = (scope: Construct) => { ... return IntegrationBuilder.rest({ pattern: 'shared', ... }); };}CDK와 달리 통합 패턴은 생성된 모듈에 내장되어 있습니다. 통합 패턴을 변경하려면:
packages/common/terraform/src/app/apis에서 이전에 생성된 API 모듈을 삭제합니다- API를 생성한 생성기를 다른 통합 패턴으로 다시 실행합니다 (예:
--integrationPattern=shared)
isolated 패턴을 사용하면 모듈은 생성된 파일에서 작업을 읽습니다:
locals { operations_file = "${path.module}/../../../generated/my-api/operations.json" operations = fileexists(local.operations_file) ? jsondecode(file(local.operations_file)) : {}}이 파일은 API에서 생성되므로 수동으로 편집할 필요가 없습니다. API 애플리케이션 코드에 작업을 추가하면 다음 배포 시 라우트와 Lambda 함수가 추가됩니다. 기본적으로 .gitignore되어 있습니다. 체크인하려면 항목을 제거하세요.
Terraform REST API 경로 깊이 제한
섹션 제목: “Terraform REST API 경로 깊이 제한”액세스 권한 부여 (IAM 전용)
섹션 제목: “액세스 권한 부여 (IAM 전용)”다음과 같이 API에 대한 액세스 권한을 부여할 수 있습니다:
api.grantInvokeAccess(myIdentityPool.authenticatedRole);# Create an IAM policy to allow invoking the APIresource "aws_iam_policy" "api_invoke_policy" { name = "MyApiInvokePolicy" description = "Policy to allow invoking the tRPC API"
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = "execute-api:Invoke" Resource = "${module.my_api.api_execution_arn}/*/*" } ] })}
# Attach the policy to an IAM role (e.g., for authenticated users)resource "aws_iam_role_policy_attachment" "api_invoke_access" { role = aws_iam_role.authenticated_user_role.name policy_arn = aws_iam_policy.api_invoke_policy.arn}
# Or attach to an existing role by nameresource "aws_iam_role_policy_attachment" "api_invoke_access_existing" { role = "MyExistingRole" policy_arn = aws_iam_policy.api_invoke_policy.arn}IAM 정책에 사용할 수 있는 API 모듈의 주요 출력은 다음과 같습니다:
module.my_api.api_execution_arn- execute-api:Invoke 권한 부여용module.my_api.api_arn- API Gateway ARNmodule.my_api.lambda_function_arn- Lambda 함수 ARN
번들 대상
섹션 제목: “번들 대상”제너레이터는 Rolldown을 사용하여 배포 패키지를 생성하는 bundle 타겟을 자동으로 구성합니다:
pnpm nx bundle <project-name>yarn nx bundle <project-name>npx nx bundle <project-name>bunx nx bundle <project-name>Rolldown 구성은 rolldown.config.ts에서 찾을 수 있으며, 생성할 번들마다 항목이 있습니다. Rolldown은 정의된 경우 여러 번들을 병렬로 생성하는 것을 관리합니다.
로컬 tRPC 서버
섹션 제목: “로컬 tRPC 서버”serve 대상을 사용하여 API용 로컬 서버를 실행할 수 있습니다. 예를 들어:
pnpm nx serve my-apiyarn nx serve my-apinpx nx serve my-apibunx nx serve my-api로컬 서버의 진입점은 src/local-server.ts입니다.
API를 변경하면 자동으로 다시 로드됩니다.
tRPC API 호출
섹션 제목: “tRPC API 호출”타입 안전 방식으로 API를 호출하기 위해 tRPC 클라이언트를 생성할 수 있습니다. 다른 백엔드에서 tRPC API를 호출하는 경우 src/client/index.ts의 클라이언트를 사용할 수 있습니다. 예를 들어:
import { createMyApiClient } from '@my-scope/my-api';
const client = createMyApiClient({ url: 'https://my-api-url.example.com/' });
await client.echo.query({ message: 'Hello world!' });React 웹사이트에서 API를 호출하는 경우 Connection 생성기를 사용하여 클라이언트를 구성하는 것을 고려하세요.
추가 정보
섹션 제목: “추가 정보”tRPC에 대한 자세한 내용은 tRPC 문서를 참조하세요.
connection 생성기를 사용하여 이 프로젝트를 작업 공간의 다른 프로젝트와 통합하세요. 다음 연결에는 이 프로젝트가 포함됩니다: