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 메트릭을 포함한 관측 가능성을 위해 AWS Lambda Powertools를 설정합니다.
사용 방법
섹션 제목: “사용 방법”tRPC API 생성
섹션 제목: “tRPC API 생성”다음 두 가지 방법으로 새로운 tRPC API를 생성할 수 있습니다:
- 설치 Nx Console VSCode Plugin 아직 설치하지 않았다면
- VSCode에서 Nx 콘솔 열기
- 클릭
Generate (UI)
"Common Nx Commands" 섹션에서 - 검색
@aws/nx-plugin - ts#trpc-api
- 필수 매개변수 입력
- 클릭
Generate
pnpm nx g @aws/nx-plugin:ts#trpc-api
yarn nx g @aws/nx-plugin:ts#trpc-api
npx nx g @aws/nx-plugin:ts#trpc-api
bunx nx g @aws/nx-plugin:ts#trpc-api
어떤 파일이 변경될지 확인하기 위해 드라이 런을 수행할 수도 있습니다
pnpm nx g @aws/nx-plugin:ts#trpc-api --dry-run
yarn nx g @aws/nx-plugin:ts#trpc-api --dry-run
npx nx g @aws/nx-plugin:ts#trpc-api --dry-run
bunx nx g @aws/nx-plugin:ts#trpc-api --dry-run
매개변수 | 타입 | 기본값 | 설명 |
---|---|---|---|
name 필수 | string | - | The name of the API (required). Used to generate class names and file paths. |
computeType | string | ServerlessApiGatewayRestApi | The type of compute to use to deploy this API. Choose between ServerlessApiGatewayRestApi (default) or ServerlessApiGatewayHttpApi. |
auth | string | IAM | The method used to authenticate with your API. Choose between IAM (default), Cognito or None. |
directory | string | packages | The directory to store the application in. |
iacProvider | string | Inherit | The preferred IaC provider. By default this is inherited from your initial selection. |
생성기 출력
섹션 제목: “생성기 출력”생성기는 <directory>/<api-name>
디렉토리에 다음 프로젝트 구조를 생성합니다:
디렉터리src
- init.ts 백엔드 tRPC 초기화
- router.ts tRPC 라우터 정의 (Lambda 핸들러 API 진입점)
디렉터리schema Zod를 사용한 스키마 정의
- echo.ts “echo” 프로시저의 입력 및 출력 예제 정의
디렉터리procedures API가 노출하는 프로시저(또는 작업)
- echo.ts 예제 프로시저
디렉터리middleware
- error.ts 오류 처리 미들웨어
- logger.ts AWS Powertools for Lambda 로깅 설정 미들웨어
- tracer.ts AWS Powertools for Lambda 추적 설정 미들웨어
- metrics.ts AWS Powertools for Lambda 메트릭 설정 미들웨어
- local-server.ts 로컬 개발 서버용 tRPC 독립 실행형 어댑터 진입점
디렉터리client
- index.ts 기계 간 API 호출을 위한 타입 안전 클라이언트
- tsconfig.json TypeScript 구성
- project.json 프로젝트 구성 및 빌드 대상
인프라
섹션 제목: “인프라”이 생성기는 선택한 iacProvider
기반으로 인프라를 코드 형태로 제공하므로, packages/common
디렉터리에 관련 CDK 구축 요소 또는 Terraform 모듈을 포함하는 프로젝트를 생성합니다.
공통 인프라스트럭처 코드 프로젝트의 구조는 다음과 같습니다:
디렉터리packages/common/constructs
디렉터리src
디렉터리app/ 특정 프로젝트/생성기에 종속적인 인프라를 위한 구축 요소
- …
디렉터리core/
app
내 구축 요소에서 재사용되는 일반적 구축 요소- …
- index.ts
app
의 구축 요소를 익스포트하는 진입점
- project.json 프로젝트 빌드 대상 및 구성
디렉터리packages/common/terraform
디렉터리src
디렉터리app/ 특정 프로젝트/생성기 전용 Terraform 모듈
- …
디렉터리core/
app
내 모듈에서 재사용되는 일반적 모듈- …
- project.json 프로젝트 빌드 대상 및 구성
API 배포를 위해 다음 파일들이 생성됩니다:
디렉터리packages/common/constructs/src
디렉터리app
디렉터리apis
- <project-name>.ts API를 배포하기 위한 CDK construct
디렉터리core
디렉터리api
- http-api.ts HTTP API 배포를 위한 CDK construct (HTTP API 배포를 선택한 경우)
- rest-api.ts REST API 배포를 위한 CDK construct (REST API 배포를 선택한 경우)
- utils.ts API constructs를 위한 유틸리티
디렉터리packages/common/terraform/src
디렉터리app
디렉터리apis
디렉터리<project-name>
- <project-name>.tf API를 배포하기 위한 모듈
디렉터리core
디렉터리api
디렉터리http-api
- http-api.tf HTTP API 배포를 위한 모듈 (HTTP API 배포를 선택한 경우)
디렉터리rest-api
- rest-api.tf REST API 배포를 위한 모듈 (REST API 배포를 선택한 경우)
tRPC API 구현
섹션 제목: “tRPC API 구현”개요적으로, tRPC API는 특정 프로시저로 요청을 위임하는 라우터로 구성됩니다. 각 프로시저는 Zod 스키마로 정의된 입력과 출력을 가집니다.
스키마
섹션 제목: “스키마”src/schema
디렉토리에는 클라이언트와 서버 코드 간에 공유되는 타입들이 포함되어 있습니다. 이 패키지에서는 TypeScript 우선 스키마 선언 및 검증 라이브러리인 Zod를 사용하여 이러한 타입을 정의합니다.
예제 스키마는 다음과 같을 수 있습니다:
import { z } from 'zod';
// 스키마 정의export const UserSchema = z.object({ name: z.string(), height: z.number(), dateOfBirth: z.string().datetime(),});
// 해당 TypeScript 타입export type User = z.TypeOf<typeof UserSchema>;
위 스키마가 주어지면 User
타입은 다음 TypeScript 인터페이스와 동일합니다:
interface User { name: string; height: number; dateOfBirth: string;}
스키마는 서버와 클라이언트 코드 모두에서 공유되므로 API에서 사용하는 구조를 변경할 때 단일 위치에서 업데이트할 수 있습니다.
스키마는 런타임에 tRPC API에 의해 자동으로 검증되므로 백엔드에서 수동으로 검증 로직을 작성할 필요가 없습니다.
Zod는 .merge
, .pick
, .omit
등과 같은 스키마를 결합하거나 파생시키는 강력한 유틸리티를 제공합니다. 자세한 내용은 Zod 문서 웹사이트에서 확인할 수 있습니다.
라우터 및 프로시저
섹션 제목: “라우터 및 프로시저”API의 진입점은 src/router.ts
에서 찾을 수 있습니다. 이 파일은 호출되는 작업에 따라 요청을 “프로시저”로 라우팅하는 Lambda 핸들러를 포함합니다. 각 프로시저는 예상 입력, 출력 및 구현을 정의합니다.
생성된 샘플 라우터에는 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) => ({ result: 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 API 사용자 정의
섹션 제목: “tRPC API 사용자 정의”오류 처리
섹션 제목: “오류 처리”구현에서 TRPCError
를 발생시켜 클라이언트에 오류 응답을 반환할 수 있습니다. 이는 오류 유형을 나타내는 code
를 받습니다. 예를 들어:
throw new TRPCError({ code: 'NOT_FOUND', message: '요청한 리소스를 찾을 수 없습니다',});
작업 구성
섹션 제목: “작업 구성”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('작업이 입력과 함께 호출됨', 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 구현에서 접근할 수 있습니다. 이를 사용하여 API 요청의 성능 및 흐름에 대한 자세한 통찰력을 제공하는 AWS X-Ray 추적을 추가할 수 있습니다. 예를 들어:
export const echo = publicProcedure .input(...) .output(...) .query(async (opts) => { const subSegment = opts.ctx.tracer.getSegment()!.addNewSubsegment('MyAlgorithm'); // ... 캡처할 알고리즘 로직 subSegment.close();
return ...; });
자세한 내용은 AWS Lambda Powertools Tracer 문서를 참조하세요.
사용자 정의 미들웨어 구현
섹션 제목: “사용자 정의 미들웨어 구현”미들웨어를 구현하여 프로시저에 제공되는 컨텍스트에 추가 값을 추가할 수 있습니다.
예를 들어, API 호출 사용자에 대한 세부 정보를 추출하는 미들웨어를 src/middleware/identity.ts
에 구현해 보겠습니다.
이 예제는 auth
가 IAM
으로 설정되었다고 가정합니다. Cognito 인증의 경우 이벤트에서 관련 클레임을 추출하는 것이 더 간단합니다.
먼저 컨텍스트에 추가할 내용을 정의합니다:
export interface IIdentityContext { identity?: { sub: string; username: string; };}
이 추가 선택적 속성이 올바르게 이 미들웨어를 구성한 프로시저에서 정의됨을 tRPC가 관리합니다.
다음으로 미들웨어 자체를 구현합니다. 이는 다음 구조를 가집니다:
export const createIdentityPlugin = () => { const t = initTRPC.context<...>().create(); return t.procedure.use(async (opts) => { // 프로시저 전에 실행할 로직 추가
const response = await opts.next(...);
// 프로시저 후에 실행할 로직 추가
return response; });};
이 경우 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 { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';import { 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: `호출 사용자를 확인할 수 없음`, }); }
const { Users } = await cognito.listUsers({ // Lambda 환경에 사용자 풀 ID가 구성되었다고 가정 UserPoolId: process.env.USER_POOL_ID!, Limit: 1, Filter: `sub="${sub}"`, });
if (!Users || Users.length !== 1) { throw new TRPCError({ code: 'FORBIDDEN', message: `주체 ID ${sub}에 해당하는 사용자를 찾을 수 없음`, }); }
// 컨텍스트에 identity를 제공하여 다른 프로시저에서 사용 가능 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 { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';import { 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: `호출 사용자를 확인할 수 없음`, }); }
const { Users } = await cognito.listUsers({ // Lambda 환경에 사용자 풀 ID가 구성되었다고 가정 UserPoolId: process.env.USER_POOL_ID!, Limit: 1, Filter: `sub="${sub}"`, });
if (!Users || Users.length !== 1) { throw new TRPCError({ code: 'FORBIDDEN', message: `주체 ID ${sub}에 해당하는 사용자를 찾을 수 없음`, }); }
// 컨텍스트에 identity를 제공하여 다른 프로시저에서 사용 가능 return await opts.next({ ctx: { ...opts.ctx, identity: { sub, username: Users[0].Username!, }, }, }); });};
tRPC API 배포
섹션 제목: “tRPC API 배포”tRPC API 생성기는 선택한 iacProvider
에 따라 CDK 또는 Terraform 인프라 코드를 생성합니다. 이를 사용하여 tRPC API를 배포할 수 있습니다.
common/constructs
폴더에 API 배포를 위한 CDK 구성이 있습니다. CDK 애플리케이션에서 이를 사용할 수 있습니다. 예를 들어:
import { MyApi } from ':my-scope/common-constructs`;
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { // 스택에 API 추가 const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(), }); }}
이 설정은 선택한 auth
방법을 기반으로 AWS API Gateway REST 또는 HTTP API, 비즈니스 로직을 위한 AWS Lambda 함수 및 인증을 포함한 API 인프라를 구성합니다.
common/terraform
폴더에 API 배포를 위한 Terraform 모듈이 있습니다. Terraform 구성에서 이를 사용할 수 있습니다:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# Lambda 함수를 위한 환경 변수 env = { ENVIRONMENT = var.environment LOG_LEVEL = "INFO" }
# 필요한 경우 추가 IAM 정책 additional_iam_policy_statements = [ # API에 필요한 추가 권한 추가 ]
tags = local.common_tags}
이 설정은 다음을 구성합니다:
- 모든 tRPC 프로시저를 제공하는 AWS Lambda 함수
- 함수 트리거로 사용되는 API Gateway HTTP/REST API
- IAM 역할 및 권한
- CloudWatch 로그 그룹
- X-Ray 추적 구성
- CORS 구성
Terraform 모듈은 사용할 수 있는 여러 출력을 제공합니다:
# API 엔드포인트 접근output "api_url" { value = module.my_api.stage_invoke_url}
# Lambda 함수 세부 정보 접근output "lambda_function_name" { value = module.my_api.lambda_function_name}
# 추가 권한 부여를 위한 IAM 역할 접근output "lambda_execution_role_arn" { value = module.my_api.lambda_execution_role_arn}
모듈에 변수를 전달하여 CORS 설정을 사용자 정의할 수 있습니다:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# 사용자 정의 CORS 구성 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}
REST/HTTP API CDK 구문은 각 작업에 대한 통합을 정의하기 위한 타입 안전 인터페이스를 제공하도록 구성됩니다.
기본 통합
섹션 제목: “기본 통합”정적 defaultIntegrations
를 사용하여 각 작업에 대해 개별 AWS Lambda 함수를 정의하는 기본 패턴을 활용할 수 있습니다:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
Terraform 모듈은 단일 Lambda 함수를 사용하는 라우터 패턴을 자동으로 적용합니다. 추가 구성이 필요하지 않습니다:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# 모듈은 모든 API 작업을 처리하는 단일 Lambda 함수를 자동 생성합니다 tags = local.common_tags}
통합 접근
섹션 제목: “통합 접근”API 구문의 integrations
속성을 통해 타입 안전 방식으로 기본 AWS Lambda 함수에 접근할 수 있습니다. 예를 들어 API에 sayHello
작업이 정의된 경우 이 함수에 권한을 추가하려면 다음과 같이 할 수 있습니다:
const api = new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this).build(),});
// sayHello는 API에 정의된 작업으로 타입이 지정됩니다api.integrations.sayHello.handler.addToRolePolicy(new PolicyStatement({ effect: Effect.ALLOW, actions: [...], resources: [...],}));
Terraform 라우터 패턴에서는 단일 Lambda 함수만 존재합니다. 모듈 출력을 통해 접근할 수 있습니다:
# 단일 Lambda 함수에 추가 권한 부여resource "aws_iam_role_policy" "additional_permissions" { name = "additional-api-permissions" role = module.my_api.lambda_execution_role_name
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = [ "s3:GetObject", "s3:PutObject" ] Resource = "arn:aws:s3:::my-bucket/*" } ] })}
기본 옵션 사용자 정의
섹션 제목: “기본 옵션 사용자 정의”withDefaultOptions
메서드를 사용하여 기본 통합 생성 시 사용되는 옵션을 사용자 정의할 수 있습니다. 예를 들어 모든 Lambda 함수를 VPC에 배치하려면:
const vpc = new Vpc(this, 'Vpc', ...);
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withDefaultOptions({ vpc, }) .build(),});
VPC 구성과 같은 옵션을 사용자 정의하려면 생성된 Terraform 모듈을 수정해야 합니다. 모든 Lambda 함수에 VPC 지원을 추가하려면:
# VPC 변수 추가variable "vpc_subnet_ids" { description = "Lambda 함수용 VPC 서브넷 ID 목록" type = list(string) default = []}
variable "vpc_security_group_ids" { description = "Lambda 함수용 VPC 보안 그룹 ID 목록" type = list(string) default = []}
# Lambda 함수 리소스 업데이트resource "aws_lambda_function" "api_lambda" { # ... 기존 구성 ...
# VPC 구성 추가 vpc_config { subnet_ids = var.vpc_subnet_ids security_group_ids = var.vpc_security_group_ids }}
다음과 같이 VPC 구성으로 모듈을 사용합니다:
module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# VPC 구성 vpc_subnet_ids = [aws_subnet.private_a.id, aws_subnet.private_b.id] vpc_security_group_ids = [aws_security_group.lambda_sg.id]
tags = local.common_tags}
통합 재정의
섹션 제목: “통합 재정의”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(),});
// 이후 다른 파일에서 정의한 bucket 속성에 타입 안전 방식으로 접근 가능api.integrations.getFile.bucket.grantRead(...);
인증자 재정의
섹션 제목: “인증자 재정의”통합에 options
를 제공하여 Cognito 인증과 같은 특정 메서드 옵션을 재정의할 수 있습니다. 예를 들어 getDocumentation
작업에 Cognito 인증을 사용하려면:
new MyApi(this, 'MyApi', { integrations: MyApi.defaultIntegrations(this) .withOverrides({ getDocumentation: { integration: new HttpIntegration('https://example.com/documentation'), options: { authorizer: new CognitoUserPoolsAuthorizer(...) // REST용 또는 HttpUserPoolAuthorizer (HTTP API) } }, }) .build(),});
명시적 통합
섹션 제목: “명시적 통합”기본 통합 대신 각 작업에 직접 통합을 제공할 수 있습니다. 이는 각 작업이 다른 유형의 통합을 사용해야 하거나 새 작업 추가 시 타입 오류를 수신하려는 경우 유용합니다:
new MyApi(this, 'MyApi', { integrations: { sayHello: { integration: new LambdaIntegration(...), }, getDocumentation: { integration: new HttpIntegration(...), }, },});
Terraform에서 작업별 명시적 통합을 사용하려면 기본 프록시 통합을 작업별 특정 통합으로 교체하도록 생성된 앱별 모듈을 수정해야 합니다.
packages/common/terraform/src/app/apis/my-api/my-api.tf
편집:
- 기본 프록시 경로 제거 (예:
resource "aws_apigatewayv2_route" "proxy_routes"
) - 단일 Lambda 함수를 작업별 개별 함수로 교체
- 동일 ZIP 번들 재사용하여 작업별 특정 통합 및 경로 생성:
# 기본 단일 Lambda 함수 제거 resource "aws_lambda_function" "api_lambda" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApiHandler" role = aws_iam_role.lambda_execution_role.arn handler = "index.handler" runtime = "nodejs22.x" timeout = 30 # ... 나머지 구성 }
# 기본 프록시 통합 제거 resource "aws_apigatewayv2_integration" "lambda_integration" { api_id = module.http_api.api_id integration_type = "AWS_PROXY" integration_uri = aws_lambda_function.api_lambda.invoke_arn # ... 나머지 구성 }
# 기본 프록시 경로 제거 resource "aws_apigatewayv2_route" "proxy_routes" { for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]) api_id = module.http_api.api_id route_key = "${each.key} /{proxy+}" target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}" # ... 나머지 구성 }
# 동일 번들 사용 작업별 Lambda 함수 추가 resource "aws_lambda_function" "say_hello_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-SayHello" role = aws_iam_role.lambda_execution_role.arn handler = "sayHello.handler" # 이 작업용 특정 핸들러 runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = merge({ AWS_CONNECTION_REUSE_ENABLED = "1" }, var.env) }
tags = var.tags }
resource "aws_lambda_function" "get_documentation_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-GetDocumentation" role = aws_iam_role.lambda_execution_role.arn handler = "getDocumentation.handler" # 이 작업용 특정 핸들러 runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = merge({ AWS_CONNECTION_REUSE_ENABLED = "1" }, var.env) }
tags = var.tags }
# 작업별 특정 통합 추가 resource "aws_apigatewayv2_integration" "say_hello_integration" { api_id = module.http_api.api_id integration_type = "AWS_PROXY" integration_uri = aws_lambda_function.say_hello_handler.invoke_arn payload_format_version = "2.0" timeout_milliseconds = 30000 }
resource "aws_apigatewayv2_integration" "get_documentation_integration" { api_id = module.http_api.api_id integration_type = "HTTP_PROXY" integration_uri = "https://example.com/documentation" integration_method = "GET" }
# 작업별 특정 경로 추가 resource "aws_apigatewayv2_route" "say_hello_route" { api_id = module.http_api.api_id route_key = "POST /sayHello" target = "integrations/${aws_apigatewayv2_integration.say_hello_integration.id}" authorization_type = "AWS_IAM" }
resource "aws_apigatewayv2_route" "get_documentation_route" { api_id = module.http_api.api_id route_key = "GET /documentation" target = "integrations/${aws_apigatewayv2_integration.get_documentation_integration.id}" authorization_type = "NONE" }
# 각 함수에 Lambda 권한 추가 resource "aws_lambda_permission" "say_hello_permission" { statement_id = "AllowExecutionFromAPIGateway-SayHello" action = "lambda:InvokeFunction" function_name = aws_lambda_function.say_hello_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.http_api.api_execution_arn}/*/*" }
resource "aws_lambda_permission" "get_documentation_permission" { statement_id = "AllowExecutionFromAPIGateway-GetDocumentation" action = "lambda:InvokeFunction" function_name = aws_lambda_function.get_documentation_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.http_api.api_execution_arn}/*/*" }
# 기본 단일 Lambda 함수 제거 resource "aws_lambda_function" "api_lambda" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApiHandler" role = aws_iam_role.lambda_execution_role.arn handler = "index.handler" runtime = "nodejs22.x" timeout = 30 # ... 나머지 구성 }
# 기본 프록시 통합 제거 resource "aws_apigatewayv2_integration" "lambda_integration" { api_id = module.http_api.api_id integration_type = "AWS_PROXY" integration_uri = aws_lambda_function.api_lambda.invoke_arn # ... 나머지 구성 }
# 기본 프록시 경로 제거 resource "aws_apigatewayv2_route" "proxy_routes" { for_each = toset(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]) api_id = module.http_api.api_id route_key = "${each.key} /{proxy+}" target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}" # ... 나머지 구성 }
# 동일 번들 사용 작업별 Lambda 함수 추가 resource "aws_lambda_function" "say_hello_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-SayHello" role = aws_iam_role.lambda_execution_role.arn handler = "sayHello.handler" # 이 작업용 특정 핸들러 runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = merge({ AWS_CONNECTION_REUSE_ENABLED = "1" }, var.env) }
tags = var.tags }
resource "aws_lambda_function" "get_documentation_handler" { filename = data.archive_file.lambda_zip.output_path function_name = "MyApi-GetDocumentation" role = aws_iam_role.lambda_execution_role.arn handler = "getDocumentation.handler" # 이 작업용 특정 핸들러 runtime = "nodejs22.x" timeout = 30 source_code_hash = data.archive_file.lambda_zip.output_base64sha256
tracing_config { mode = "Active" }
environment { variables = merge({ AWS_CONNECTION_REUSE_ENABLED = "1" }, var.env) }
tags = var.tags }
# 작업별 리소스 및 메서드 추가 resource "aws_api_gateway_resource" "say_hello_resource" { rest_api_id = module.rest_api.api_id parent_id = module.rest_api.api_root_resource_id path_part = "sayHello" }
resource "aws_api_gateway_method" "say_hello_method" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.say_hello_resource.id http_method = "POST" authorization = "AWS_IAM" }
resource "aws_api_gateway_integration" "say_hello_integration" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.say_hello_resource.id http_method = aws_api_gateway_method.say_hello_method.http_method
integration_http_method = "POST" type = "AWS_PROXY" uri = aws_lambda_function.say_hello_handler.invoke_arn }
resource "aws_api_gateway_resource" "get_documentation_resource" { rest_api_id = module.rest_api.api_id parent_id = module.rest_api.api_root_resource_id path_part = "documentation" }
resource "aws_api_gateway_method" "get_documentation_method" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.get_documentation_resource.id http_method = "GET" authorization = "NONE" }
resource "aws_api_gateway_integration" "get_documentation_integration" { rest_api_id = module.rest_api.api_id resource_id = aws_api_gateway_resource.get_documentation_resource.id http_method = aws_api_gateway_method.get_documentation_method.http_method
integration_http_method = "GET" type = "HTTP" uri = "https://example.com/documentation" }
# 새 통합에 종속성 업데이트~ resource "aws_api_gateway_deployment" "api_deployment" { rest_api_id = module.rest_api.api_id
depends_on = [ aws_api_gateway_integration.lambda_integration, aws_api_gateway_integration.say_hello_integration, aws_api_gateway_integration.get_documentation_integration, ]
lifecycle { create_before_destroy = true }
triggers = { redeployment = sha1(jsonencode([ aws_api_gateway_integration.say_hello_integration, aws_api_gateway_integration.get_documentation_integration, ])) } }
# 각 함수에 Lambda 권한 추가 resource "aws_lambda_permission" "say_hello_permission" { statement_id = "AllowExecutionFromAPIGateway-SayHello" action = "lambda:InvokeFunction" function_name = aws_lambda_function.say_hello_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.rest_api.api_execution_arn}/*/*" }
resource "aws_lambda_permission" "get_documentation_permission" { statement_id = "AllowExecutionFromAPIGateway-GetDocumentation" action = "lambda:InvokeFunction" function_name = aws_lambda_function.get_documentation_handler.function_name principal = "apigateway.amazonaws.com" source_arn = "${module.rest_api.api_execution_arn}/*/*" }
라우터 패턴
섹션 제목: “라우터 패턴”모든 API 요청을 처리하는 단일 Lambda 함수를 배포하려면 defaultIntegrations
메서드를 수정하여 작업당 하나가 아닌 단일 함수를 생성할 수 있습니다:
export class MyApi<...> extends ... {
public static defaultIntegrations = (scope: Construct) => { const router = new Function(scope, 'RouterHandler', { ... }); return IntegrationBuilder.rest({ ... defaultIntegrationOptions: {}, buildDefaultIntegration: (op) => { return { // 모든 통합에서 동일한 라우터 Lambda 핸들러 참조 integration: new LambdaIntegration(router), }; }, }); };}
router
함수를 메서드 내부에서 생성하는 대신 defaultIntegrations
의 매개변수로 정의하는 것과 같은 다른 방식으로 코드를 수정할 수도 있습니다.
Terraform 모듈은 기본적으로 라우터 패턴을 사용합니다. 이는 기본적이며 유일하게 지원되는 접근 방식입니다. 생성된 모듈은 모든 API 작업을 처리하는 단일 Lambda 함수를 생성합니다.
기본 모듈을 인스턴스화하여 라우터 패턴을 얻을 수 있습니다:
# 기본 라우터 패턴 - 모든 작업을 처리하는 단일 Lambda 함수module "my_api" { source = "../../common/terraform/src/app/apis/my-api"
# 단일 Lambda 함수가 모든 작업을 자동으로 처리 tags = local.common_tags}
접근 권한 부여 (IAM 전용)
섹션 제목: “접근 권한 부여 (IAM 전용)”IAM
인증을 선택한 경우 API에 대한 접근 권한을 부여할 수 있습니다:
api.grantInvokeAccess(myIdentityPool.authenticatedRole);
# API 호출을 허용하는 IAM 정책 생성resource "aws_iam_policy" "api_invoke_policy" { name = "MyApiInvokePolicy" description = "tRPC API 호출을 허용하는 정책"
policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Action = "execute-api:Invoke" Resource = "${module.my_api.api_execution_arn}/*/*" } ] })}
# IAM 역할에 정책 첨부 (예: 인증된 사용자)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}
# 기존 역할에 이름으로 첨부resource "aws_iam_role_policy_attachment" "api_invoke_access_existing" { role = "MyExistingRole" policy_arn = aws_iam_policy.api_invoke_policy.arn}
API 모듈에서 IAM 정책에 사용할 수 있는 주요 출력:
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 run <project-name>:bundle
yarn nx run <project-name>:bundle
npx nx run <project-name>:bundle
bunx nx run <project-name>:bundle
Rolldown 구성은 rolldown.config.ts
에서 확인할 수 있으며, 생성할 각 번들별로 엔트리가 존재합니다. 정의된 경우 Rolldown은 여러 번들을 병렬로 생성하는 작업을 관리합니다.
로컬 tRPC 서버
섹션 제목: “로컬 tRPC 서버”serve
대상을 사용하여 API용 로컬 서버를 실행할 수 있습니다. 예를 들어:
pnpm nx run @my-scope/my-api:serve
yarn nx run @my-scope/my-api:serve
npx nx run @my-scope/my-api:serve
bunx nx run @my-scope/my-api:serve
로컬 서버의 진입점은 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를 호출하는 경우 API 연결 생성기를 사용하여 클라이언트를 구성하는 것을 고려하세요.
추가 정보
섹션 제목: “추가 정보”tRPC에 대한 자세한 내용은 tRPC 문서를 참조하세요.