개요
AWS API Gateway는 개발자가 모든 규모의 REST, HTTP, WebSocket API를 생성, 게시, 유지 관리할 수 있는 완전 관리형 서비스입니다. API Gateway를 사용하면 백엔드 서비스에 대한 “정문” 역할을 하는 API를 만들 수 있으며, 트래픽 관리, 권한 부여 및 액세스 제어, 모니터링, API 버전 관리 등의 기능을 제공합니다.
간단히 말해, API Gateway는 클라이언트와 백엔드 서비스 사이에서 모든 API 요청을 받아 적절한 서비스로 라우팅하고, 응답을 반환하는 중간 계층입니다. 트래픽 제어, 보안, 모니터링과 같은 공통 기능을 API Gateway에서 처리하므로 개발자는 비즈니스 로직에 집중할 수 있습니다.
배경
왜 필요한가?
마이크로서비스 아키텍처와 서버리스 컴퓨팅이 대중화되면서 다음과 같은 문제들이 생겼습니다:
1. 여러 서비스 엔드포인트 관리의 복잡성
- 클라이언트가 수십 개의 마이크로서비스와 직접 통신해야 함
- 각 서비스마다 다른 인증/권한 부여 방식
- 서비스 엔드포인트가 변경될 때마다 클라이언트 코드 수정 필요
2. 보안과 인증의 중복 구현
- 모든 서비스에서 동일한 인증 로직을 반복 구현
- API 키, JWT 토큰 검증을 각 서비스에서 개별 처리
- 일관된 보안 정책 적용의 어려움
3. 트래픽 급증 대응
- 갑작스런 트래픽 증가 시 백엔드 서비스 과부하
- 요청 제한(Rate Limiting) 기능의 필요성
- 캐싱을 통한 불필요한 백엔드 호출 감소 필요
4. 모니터링과 로깅의 분산
- 각 서비스의 로그를 개별 확인해야 함
- API 사용 패턴 분석의 어려움
- 문제 발생 시 디버깅 복잡도 증가
등장 이전의 방식
API Gateway가 등장하기 전에는:
1. 역방향 프록시 (Nginx, Apache) 사용
# Nginx 설정 예시
location /api/users {
proxy_pass http://user-service:3000;
}
location /api/orders {
proxy_pass http://order-service:3001;
}
location /api/products {
proxy_pass http://product-service:3002;
}- 수동으로 라우팅 규칙 설정
- 인증/권한 부여는 별도 구현 필요
- 확장성과 고가용성 직접 관리
2. 커스텀 API 게이트웨이 개발
// Express.js로 구현한 간단한 게이트웨이
app.use('/api/users', authenticate, (req, res) => {
// user-service로 프록시
proxy.web(req, res, { target: 'http://user-service:3000' });
});
app.use('/api/orders', authenticate, (req, res) => {
// order-service로 프록시
proxy.web(req, res, { target: 'http://order-service:3001' });
});- 인증, 로깅, 에러 처리 등을 직접 구현
- 인프라 관리 부담
- 유지보수 비용 증가
3. 각 서비스에서 개별 처리
// 모든 마이크로서비스에 중복된 코드
app.use(authenticateMiddleware);
app.use(rateLimitMiddleware);
app.use(loggingMiddleware);
app.use(corsMiddleware);
app.get('/users', (req, res) => {
// 비즈니스 로직
});- 코드 중복
- 일관성 없는 구현
- 변경 사항 적용 시 모든 서비스 수정 필요
동작 원리
핵심 메커니즘
API Gateway는 다음과 같은 단계로 API 요청을 처리합니다:
1. 클라이언트 요청 수신
- 클라이언트(웹 앱, 모바일 앱 등)가 API Gateway 엔드포인트로 HTTP 요청 전송
- 요청에는 메서드(GET, POST 등), 경로, 헤더, 본문이 포함됨
2. 요청 검증 및 변환
- API 키, IAM 역할, Lambda Authorizer 등을 통한 인증/권한 부여
- 요청 본문 검증 (스키마 검증)
- 필요 시 요청 매핑 템플릿을 통해 요청 변환
3. 백엔드 통합 실행
- Lambda 함수 호출
- HTTP 엔드포인트로 프록시
- AWS 서비스(DynamoDB, S3 등)와 직접 통합
- VPC Link를 통한 프라이빗 리소스 액세스
4. 응답 변환 및 반환
- 백엔드 응답을 응답 매핑 템플릿으로 변환
- CORS 헤더 추가
- 캐싱된 응답 반환 (설정된 경우)
- 클라이언트에게 최종 응답 전송
5. 모니터링 및 로깅
- CloudWatch에 메트릭 기록
- X-Ray를 통한 요청 추적
- 액세스 로그 저장
시각적 예시
[클라이언트 앱]
↓
HTTP 요청
↓
┌──────────────────────────────────┐
│ AWS API Gateway │
│ │
│ ┌────────────────────────────┐ │
│ │ 1. 인증/권한 부여 │ │
│ │ - API 키 검증 │ │
│ │ - IAM 역할 확인 │ │
│ │ - Lambda Authorizer │ │
│ └────────────────────────────┘ │
│ ↓ │
│ ┌────────────────────────────┐ │
│ │ 2. 요청 검증 및 변환 │ │
│ │ - 스키마 검증 │ │
│ │ - 요청 매핑 │ │
│ └────────────────────────────┘ │
│ ↓ │
│ ┌────────────────────────────┐ │
│ │ 3. 백엔드 통합 │ │
│ │ ┌──────┐ ┌──────┐ │ │
│ │ │Lambda│ │ HTTP │ │ │
│ │ └──────┘ └──────┘ │ │
│ │ ┌──────┐ ┌──────┐ │ │
│ │ │ AWS │ │ VPC │ │ │
│ │ │Service│ │Link │ │ │
│ │ └──────┘ └──────┘ │ │
│ └────────────────────────────┘ │
│ ↓ │
│ ┌────────────────────────────┐ │
│ │ 4. 응답 변환 │ │
│ │ - 응답 매핑 │ │
│ │ - 캐싱 (선택) │ │
│ └────────────────────────────┘ │
└──────────────────────────────────┘
↓
HTTP 응답
↓
[클라이언트 앱]코드로 이해하기
간단한 Lambda 통합 예제
// Lambda 함수 (백엔드)
exports.handler = async (event) => {
// API Gateway에서 전달된 요청 정보
const { httpMethod, path, queryStringParameters, body } = event;
console.log('요청 메서드:', httpMethod);
console.log('요청 경로:', path);
console.log('쿼리 파라미터:', queryStringParameters);
if (httpMethod === 'GET' && path === '/users') {
// 사용자 목록 조회
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({ users })
};
}
if (httpMethod === 'POST' && path === '/users') {
// 새 사용자 생성
const newUser = JSON.parse(body);
return {
statusCode: 201,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
message: '사용자가 생성되었습니다',
user: newUser
})
};
}
// 경로를 찾을 수 없음
return {
statusCode: 404,
body: JSON.stringify({ error: '경로를 찾을 수 없습니다' })
};
};클라이언트에서 API 호출
// API Gateway 엔드포인트 URL
const API_URL = 'https://abc123.execute-api.ap-northeast-2.amazonaws.com/prod';
// 1. 사용자 목록 조회
async function getUsers() {
try {
const response = await fetch(`${API_URL}/users`, {
method: 'GET',
headers: {
'x-api-key': 'your-api-key-here'
}
});
const data = await response.json();
console.log('사용자 목록:', data.users);
return data.users;
} catch (error) {
console.error('사용자 조회 실패:', error);
throw error;
}
}
// 2. 새 사용자 생성
async function createUser(name) {
try {
const response = await fetch(`${API_URL}/users`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'your-api-key-here'
},
body: JSON.stringify({ name })
});
if (!response.ok) {
throw new Error(`HTTP 오류: ${response.status}`);
}
const data = await response.json();
console.log('생성된 사용자:', data.user);
return data.user;
} catch (error) {
console.error('사용자 생성 실패:', error);
throw error;
}
}
// 사용 예시
async function main() {
// 사용자 목록 조회
const users = await getUsers();
// 새 사용자 생성
const newUser = await createUser('Charlie');
// 다시 목록 조회
const updatedUsers = await getUsers();
}
main();주요 특징
특징 1: 다양한 API 유형 지원
API Gateway는 세 가지 유형의 API를 지원합니다:
1. REST API
- 가장 일반적인 API 유형
- RESTful 아키텍처 원칙 준수
- 리소스 기반 엔드포인트 (/users, /orders 등)
- 완전한 API 관리 기능 (캐싱, 요청/응답 변환, SDK 생성 등)
// REST API 엔드포인트 예시
GET /users // 사용자 목록 조회
GET /users/{id} // 특정 사용자 조회
POST /users // 새 사용자 생성
PUT /users/{id} // 사용자 정보 수정
DELETE /users/{id} // 사용자 삭제2. HTTP API
- REST API보다 저렴하고 빠른 대안
- 기본 API 관리 기능만 제공
- 낮은 지연 시간
- 비용 효율적 (REST API 대비 최대 70% 저렴)
// HTTP API는 REST API와 동일한 방식으로 사용
// 하지만 일부 고급 기능(캐싱, 요청 검증 등)은 미지원
const response = await fetch(`${HTTP_API_URL}/users`, {
method: 'GET'
});3. WebSocket API
- 실시간 양방향 통신
- 채팅 애플리케이션, 게임, 실시간 대시보드에 적합
- 연결 유지 및 메시지 라우팅
// WebSocket 연결
const ws = new WebSocket('wss://abc123.execute-api.ap-northeast-2.amazonaws.com/prod');
// 연결 성공
ws.onopen = () => {
console.log('WebSocket 연결됨');
// 서버로 메시지 전송
ws.send(JSON.stringify({
action: 'sendMessage',
message: '안녕하세요!'
}));
};
// 서버로부터 메시지 수신
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('받은 메시지:', data);
};
// 연결 종료
ws.onclose = () => {
console.log('WebSocket 연결 종료됨');
};
// 오류 처리
ws.onerror = (error) => {
console.error('WebSocket 오류:', error);
};특징 2: 강력한 인증 및 권한 부여
API Gateway는 여러 인증 방식을 제공합니다:
1. API 키 기반 인증
// API 키를 헤더에 포함하여 요청
const response = await fetch(`${API_URL}/users`, {
method: 'GET',
headers: {
'x-api-key': 'abc123xyz456' // API Gateway에서 생성한 키
}
});특징:
- 간단한 구현
- 사용량 계획(Usage Plan)과 연동 가능
- 개발자별/앱별 키 발급 가능
2. IAM 권한 부여
// AWS SDK를 사용한 IAM 인증
import { SignatureV4 } from '@aws-sdk/signature-v4';
import { Sha256 } from '@aws-crypto/sha256-js';
const signer = new SignatureV4({
service: 'execute-api',
region: 'ap-northeast-2',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
},
sha256: Sha256
});
const request = {
method: 'GET',
protocol: 'https:',
hostname: 'abc123.execute-api.ap-northeast-2.amazonaws.com',
path: '/prod/users',
headers: {
'host': 'abc123.execute-api.ap-northeast-2.amazonaws.com'
}
};
const signedRequest = await signer.sign(request);
// signedRequest를 사용하여 API 호출특징:
- AWS 리소스에 대한 세밀한 권한 제어
- 임시 자격 증명 지원
- CloudTrail과 통합된 감사 로깅
3. Lambda Authorizer (커스텀 인증)
// Lambda Authorizer 함수
exports.handler = async (event) => {
// 요청 헤더에서 토큰 추출
const token = event.headers.Authorization;
try {
// JWT 토큰 검증
const decoded = verifyJWT(token);
// 인증 성공 - IAM 정책 반환
return {
principalId: decoded.userId,
policyDocument: {
Version: '2012-10-17',
Statement: [
{
Action: 'execute-api:Invoke',
Effect: 'Allow',
Resource: event.methodArn
}
]
},
context: {
// API Gateway가 백엔드로 전달할 추가 정보
userId: decoded.userId,
userRole: decoded.role
}
};
} catch (error) {
// 인증 실패
throw new Error('Unauthorized');
}
};
// Lambda 함수에서 인증 컨텍스트 사용
exports.handler = async (event) => {
// Lambda Authorizer가 전달한 컨텍스트
const userId = event.requestContext.authorizer.userId;
const userRole = event.requestContext.authorizer.userRole;
console.log('요청 사용자:', userId, userRole);
// 사용자 역할에 따른 로직 처리
if (userRole === 'admin') {
// 관리자 전용 기능
} else {
// 일반 사용자 기능
}
};특징:
- 완전한 커스텀 인증 로직 구현 가능
- OAuth 2.0, SAML 등 외부 인증 시스템 통합
- 세션 기반 인증 구현 가능
4. Cognito User Pools
// Amazon Cognito를 사용한 사용자 인증
import { CognitoUserPool, CognitoUser, AuthenticationDetails } from 'amazon-cognito-identity-js';
const poolData = {
UserPoolId: 'ap-northeast-2_ABC123',
ClientId: 'xyz789abc123'
};
const userPool = new CognitoUserPool(poolData);
// 사용자 로그인
function signIn(username, password) {
const authenticationData = {
Username: username,
Password: password
};
const authenticationDetails = new AuthenticationDetails(authenticationData);
const userData = {
Username: username,
Pool: userPool
};
const cognitoUser = new CognitoUser(userData);
return new Promise((resolve, reject) => {
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: (result) => {
// ID 토큰 획득
const idToken = result.getIdToken().getJwtToken();
resolve(idToken);
},
onFailure: (err) => {
reject(err);
}
});
});
}
// API Gateway 호출 시 토큰 사용
async function callAPI() {
const idToken = await signIn('user@example.com', 'password123');
const response = await fetch(`${API_URL}/users`, {
method: 'GET',
headers: {
'Authorization': idToken
}
});
return response.json();
}특징:
- 완전 관리형 사용자 디렉터리
- 소셜 로그인 (Google, Facebook 등) 지원
- MFA(다중 인증) 지원
- 사용자 관리 기능 내장
특징 3: 요청 및 응답 변환
API Gateway는 요청과 응답을 변환하여 클라이언트와 백엔드 간의 계약을 조정할 수 있습니다.
요청 매핑 템플릿 (VTL - Velocity Template Language)
## API Gateway 요청 매핑 템플릿
## 클라이언트 요청을 Lambda 함수에 맞게 변환
{
"httpMethod": "$context.httpMethod",
"resourcePath": "$context.resourcePath",
"userId": "$context.authorizer.userId",
"requestTime": "$context.requestTime",
"body": $input.json('),
"queryParams": {
#foreach($param in $input.params().querystring.keySet())
"$param": "$util.escapeJavaScript($input.params().querystring.get($param))"
#if($foreach.hasNext),#end
#end
}
}응답 매핑 템플릿
## Lambda 응답을 클라이언트에 맞게 변환
#set($inputRoot = $input.path('))
{
"success": true,
"timestamp": "$context.requestTime",
"data": $inputRoot.body,
"metadata": {
"requestId": "$context.requestId",
"statusCode": $inputRoot.statusCode
}
}실제 사용 예시
// Lambda 함수는 단순한 응답만 반환
exports.handler = async (event) => {
return {
statusCode: 200,
body: JSON.stringify({
users: [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
]
})
};
};
// API Gateway가 응답 매핑 템플릿을 적용한 후
// 클라이언트가 받는 최종 응답:
{
"success": true,
"timestamp": "01/Jan/2026:10:30:45 +0000",
"data": {
"users": [
{ "id": 1, "name": "Alice" },
{ "id": 2, "name": "Bob" }
]
},
"metadata": {
"requestId": "abc-123-xyz-789",
"statusCode": 200
}
}특징 4: 캐싱
API Gateway는 응답을 캐싱하여 백엔드 호출을 줄이고 지연 시간을 개선할 수 있습니다.
// AWS SDK를 사용한 캐싱 설정
const apigateway = new AWS.APIGateway();
await apigateway.updateStage({
restApiId: 'abc123',
stageName: 'prod',
patchOperations: [
{
op: 'replace',
path: '/cacheClusterEnabled',
value: 'true'
},
{
op: 'replace',
path: '/cacheClusterSize',
value: '0.5' // 0.5GB 캐시
}
]
}).promise();캐시 무효화
// 클라이언트에서 캐시 무효화 요청
const response = await fetch(`${API_URL}/users`, {
method: 'GET',
headers: {
'Cache-Control': 'max-age=0' // 캐시 사용 안 함
}
});캐시 키 커스터마이징
// 쿼리 파라미터 기반 캐싱
// GET /users?page=1 과 GET /users?page=2 는 다른 캐시
const page1 = await fetch(`${API_URL}/users?page=1`); // 백엔드 호출
const page1Again = await fetch(`${API_URL}/users?page=1`); // 캐시에서 반환
const page2 = await fetch(`${API_URL}/users?page=2`); // 백엔드 호출특징 5: 요청 제한 (Throttling) 및 사용량 계획
요청 제한
// AWS SDK를 사용한 사용량 계획 생성
const apigateway = new AWS.APIGateway();
const usagePlan = await apigateway.createUsagePlan({
name: 'Basic Plan',
description: '무료 사용자용 기본 플랜',
apiStages: [
{
apiId: 'abc123',
stage: 'prod'
}
],
throttle: {
rateLimit: 100, // 초당 100개 요청
burstLimit: 200 // 버스트 200개
},
quota: {
limit: 10000, // 월 10,000개 요청
period: 'MONTH'
}
}).promise();
console.log('사용량 계획 생성됨:', usagePlan.id);클라이언트 요청 제한 처리
async function callAPIWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
if (response.status === 429) {
// 요청 제한 초과 (Too Many Requests)
const retryAfter = response.headers.get('Retry-After') || 1;
console.log(`요청 제한 초과. ${retryAfter}초 후 재시도...`);
await new Promise(resolve =>
setTimeout(resolve, retryAfter * 1000)
);
continue; // 재시도
}
return response;
} catch (error) {
if (i === maxRetries - 1) throw error;
console.log(`오류 발생. 재시도 ${i + 1}/${maxRetries}`);
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
// 사용 예시
const response = await callAPIWithRetry(`${API_URL}/users`, {
method: 'GET',
headers: {
'x-api-key': 'your-api-key'
}
});실제 사용 사례
사례 1: 서버리스 웹 애플리케이션 백엔드
시나리오: React로 만든 SPA(Single Page Application)의 백엔드 API 구축
아키텍처:
[React 앱 (S3 + CloudFront)]
↓
HTTPS 요청
↓
[API Gateway]
↓
┌────┴────┐
↓ ↓
[Lambda] [Lambda]
↓ ↓
[DynamoDB] [S3]구현 예시:
// 1. Lambda 함수 - 사용자 데이터 CRUD
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();
exports.handler = async (event) => {
const { httpMethod, path, body } = event;
const userId = event.requestContext.authorizer.userId;
try {
switch (httpMethod) {
case 'GET':
// 사용자 프로필 조회
const result = await dynamodb.get({
TableName: 'Users',
Key: { userId }
}).promise();
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify(result.Item)
};
case 'PUT':
// 사용자 프로필 업데이트
const updates = JSON.parse(body);
await dynamodb.update({
TableName: 'Users',
Key: { userId },
UpdateExpression: 'set #name = :name, email = :email',
ExpressionAttributeNames: {
'#name': 'name'
},
ExpressionAttributeValues: {
':name': updates.name,
':email': updates.email
}
}).promise();
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({
message: '프로필이 업데이트되었습니다'
})
};
default:
return {
statusCode: 405,
body: JSON.stringify({
error: '지원하지 않는 메서드입니다'
})
};
}
} catch (error) {
console.error('오류:', error);
return {
statusCode: 500,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({
error: '서버 오류가 발생했습니다'
})
};
}
};// 2. React 앱에서 API 호출
import React, { useState, useEffect } from 'react';
function UserProfile() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const API_URL = 'https://abc123.execute-api.ap-northeast-2.amazonaws.com/prod';
useEffect(() => {
fetchUserProfile();
}, []);
async function fetchUserProfile() {
try {
setLoading(true);
const response = await fetch(`${API_URL}/profile`, {
method: 'GET',
headers: {
'Authorization': localStorage.getItem('idToken')
}
});
if (!response.ok) {
throw new Error('프로필 조회 실패');
}
const data = await response.json();
setUser(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
async function updateProfile(updates) {
try {
const response = await fetch(`${API_URL}/profile`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': localStorage.getItem('idToken')
},
body: JSON.stringify(updates)
});
if (!response.ok) {
throw new Error('프로필 업데이트 실패');
}
// 프로필 새로고침
await fetchUserProfile();
alert('프로필이 업데이트되었습니다!');
} catch (err) {
alert(`오류: ${err.message}`);
}
}
if (loading) return <div>로딩 중...</div>;
if (error) return <div>오류: {error}</div>;
if (!user) return <div>사용자를 찾을 수 없습니다</div>;
return (
<div>
<h1>{user.name}님의 프로필</h1>
<p>이메일: {user.email}</p>
<button onClick={() => updateProfile({
name: '새 이름',
email: 'new@example.com'
})}>
프로필 업데이트
</button>
</div>
);
}
export default UserProfile;장점:
- 서버 관리 불필요 (완전 서버리스)
- 자동 확장
- 사용한 만큼만 비용 지불
- HTTPS 기본 제공
- CORS 자동 처리
사례 2: 마이크로서비스 통합
시나리오: 여러 마이크로서비스를 하나의 API로 통합
아키텍처:
[모바일 앱]
↓
[API Gateway]
↓
┌──┴──┬──────┬──────┐
↓ ↓ ↓ ↓
[User] [Order] [Pay] [Notify]
Service Service Service ServiceAPI Gateway 라우팅 설정:
// AWS CDK를 사용한 API Gateway 설정
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as lambda from 'aws-cdk-lib/aws-lambda';
class MicroservicesAPI extends cdk.Stack {
constructor(scope, id, props) {
super(scope, id, props);
// API Gateway 생성
const api = new apigateway.RestApi(this, 'MicroservicesAPI', {
restApiName: 'Microservices Integration API',
description: '마이크로서비스 통합 API',
deployOptions: {
stageName: 'prod'
}
});
// Lambda 함수들
const userService = new lambda.Function(this, 'UserService', {
runtime: lambda.Runtime.NODEJS_18_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('lambda/user-service')
});
const orderService = new lambda.Function(this, 'OrderService', {
runtime: lambda.Runtime.NODEJS_18_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('lambda/order-service')
});
const paymentService = new lambda.Function(this, 'PaymentService', {
runtime: lambda.Runtime.NODEJS_18_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('lambda/payment-service')
});
// API Gateway 라우팅 설정
// /users 경로
const users = api.root.addResource('users');
users.addMethod('GET', new apigateway.LambdaIntegration(userService));
users.addMethod('POST', new apigateway.LambdaIntegration(userService));
const userId = users.addResource('{id}');
userId.addMethod('GET', new apigateway.LambdaIntegration(userService));
userId.addMethod('PUT', new apigateway.LambdaIntegration(userService));
userId.addMethod('DELETE', new apigateway.LambdaIntegration(userService));
// /orders 경로
const orders = api.root.addResource('orders');
orders.addMethod('GET', new apigateway.LambdaIntegration(orderService));
orders.addMethod('POST', new apigateway.LambdaIntegration(orderService));
// /payments 경로
const payments = api.root.addResource('payments');
payments.addMethod('POST', new apigateway.LambdaIntegration(paymentService));
}
}모바일 앱에서 사용:
// API 클라이언트 클래스
class MicroservicesClient {
constructor(baseURL, apiKey) {
this.baseURL = baseURL;
this.apiKey = apiKey;
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const headers = {
'Content-Type': 'application/json',
'x-api-key': this.apiKey,
...options.headers
};
const response = await fetch(url, {
...options,
headers
});
if (!response.ok) {
throw new Error(`API 오류: ${response.status}`);
}
return response.json();
}
// User Service
async getUsers() {
return this.request('/users', { method: 'GET' });
}
async createUser(userData) {
return this.request('/users', {
method: 'POST',
body: JSON.stringify(userData)
});
}
// Order Service
async getOrders() {
return this.request('/orders', { method: 'GET' });
}
async createOrder(orderData) {
return this.request('/orders', {
method: 'POST',
body: JSON.stringify(orderData)
});
}
// Payment Service
async processPayment(paymentData) {
return this.request('/payments', {
method: 'POST',
body: JSON.stringify(paymentData)
});
}
}
// 사용 예시
const client = new MicroservicesClient(
'https://abc123.execute-api.ap-northeast-2.amazonaws.com/prod',
'your-api-key-here'
);
async function placeOrder() {
try {
// 1. 사용자 생성
const user = await client.createUser({
name: 'John Doe',
email: 'john@example.com'
});
// 2. 주문 생성
const order = await client.createOrder({
userId: user.id,
items: [
{ productId: 'prod-1', quantity: 2 },
{ productId: 'prod-2', quantity: 1 }
]
});
// 3. 결제 처리
const payment = await client.processPayment({
orderId: order.id,
amount: order.total,
method: 'credit_card'
});
console.log('주문 완료:', {
user,
order,
payment
});
} catch (error) {
console.error('주문 실패:', error);
}
}
placeOrder();사례 3: 실시간 채팅 애플리케이션 (WebSocket)
시나리오: WebSocket API를 사용한 실시간 채팅
아키텍처:
[웹 브라우저]
↓ WebSocket
[API Gateway (WebSocket API)]
↓
[Lambda (onConnect)]
↓
[DynamoDB (연결 정보 저장)]
[Lambda (onMessage)]
↓
[모든 연결된 클라이언트에게 브로드캐스트]Lambda 함수 구현:
// 1. onConnect - 새 연결 처리
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();
exports.handler = async (event) => {
const connectionId = event.requestContext.connectionId;
try {
// 연결 정보를 DynamoDB에 저장
await dynamodb.put({
TableName: 'ChatConnections',
Item: {
connectionId,
connectedAt: new Date().toISOString()
}
}).promise();
return {
statusCode: 200,
body: 'Connected'
};
} catch (error) {
console.error('연결 실패:', error);
return {
statusCode: 500,
body: 'Failed to connect'
};
}
};// 2. onDisconnect - 연결 종료 처리
exports.handler = async (event) => {
const connectionId = event.requestContext.connectionId;
try {
// 연결 정보 삭제
await dynamodb.delete({
TableName: 'ChatConnections',
Key: { connectionId }
}).promise();
return {
statusCode: 200,
body: 'Disconnected'
};
} catch (error) {
console.error('연결 종료 실패:', error);
return {
statusCode: 500,
body: 'Failed to disconnect'
};
}
};// 3. onMessage - 메시지 처리 및 브로드캐스트
const AWS = require('aws-sdk');
const dynamodb = new AWS.DynamoDB.DocumentClient();
const apigatewaymanagementapi = new AWS.ApiGatewayManagementApi({
endpoint: process.env.WEBSOCKET_ENDPOINT
});
exports.handler = async (event) => {
const { connectionId } = event.requestContext;
const { message, username } = JSON.parse(event.body);
try {
// 모든 연결 조회
const connections = await dynamodb.scan({
TableName: 'ChatConnections'
}).promise();
// 메시지 포맷팅
const messageData = JSON.stringify({
username,
message,
timestamp: new Date().toISOString()
});
// 모든 연결된 클라이언트에게 메시지 전송
const sendPromises = connections.Items.map(async ({ connectionId: id }) => {
try {
await apigatewaymanagementapi.postToConnection({
ConnectionId: id,
Data: messageData
}).promise();
} catch (error) {
if (error.statusCode === 410) {
// 연결이 끊긴 경우 DB에서 삭제
await dynamodb.delete({
TableName: 'ChatConnections',
Key: { connectionId: id }
}).promise();
}
}
});
await Promise.all(sendPromises);
return {
statusCode: 200,
body: 'Message sent'
};
} catch (error) {
console.error('메시지 전송 실패:', error);
return {
statusCode: 500,
body: 'Failed to send message'
};
}
};클라이언트 구현:
// React 채팅 컴포넌트
import React, { useState, useEffect, useRef } from 'react';
function ChatRoom() {
const [messages, setMessages] = useState([]);
const [inputMessage, setInputMessage] = useState('');
const [username, setUsername] = useState('익명');
const [connected, setConnected] = useState(false);
const wsRef = useRef(null);
const WS_URL = 'wss://abc123.execute-api.ap-northeast-2.amazonaws.com/prod';
useEffect(() => {
// WebSocket 연결
const ws = new WebSocket(WS_URL);
wsRef.current = ws;
ws.onopen = () => {
console.log('WebSocket 연결됨');
setConnected(true);
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
setMessages(prev => [...prev, data]);
};
ws.onerror = (error) => {
console.error('WebSocket 오류:', error);
};
ws.onclose = () => {
console.log('WebSocket 연결 종료');
setConnected(false);
};
// 컴포넌트 언마운트 시 연결 종료
return () => {
ws.close();
};
}, []);
const sendMessage = () => {
if (!inputMessage.trim() || !connected) return;
const message = {
action: 'sendMessage',
message: inputMessage,
username: username
};
wsRef.current.send(JSON.stringify(message));
setInputMessage('');
};
return (
<div className="chat-room">
<div className="status">
상태: {connected ? '연결됨 ✅' : '연결 안 됨 ❌'}
</div>
<div className="username-input">
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="사용자 이름"
/>
</div>
<div className="messages">
{messages.map((msg, index) => (
<div key={index} className="message">
<strong>{msg.username}</strong>: {msg.message}
<span className="timestamp">
{new Date(msg.timestamp).toLocaleTimeString()}
</span>
</div>
))}
</div>
<div className="message-input">
<input
type="text"
value={inputMessage}
onChange={(e) => setInputMessage(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
placeholder="메시지 입력..."
disabled={!connected}
/>
<button onClick={sendMessage} disabled={!connected}>
전송
</button>
</div>
</div>
);
}
export default ChatRoom;장점과 한계
장점
✅ 완전 관리형 서비스
- 서버 프로비저닝, 패치, 유지보수 불필요
- 자동 확장 및 로드 밸런싱
- 고가용성 보장 (99.95% SLA)
// 트래픽이 10배 증가해도 설정 변경 불필요
// API Gateway가 자동으로 확장
const response = await fetch(`${API_URL}/users`);✅ 강력한 보안 기능
- AWS IAM, Cognito, Lambda Authorizer 지원
- API 키, OAuth 2.0, SAML 통합
- DDoS 보호 (AWS Shield)
- WAF(Web Application Firewall) 통합 가능
✅ 비용 효율적
- 사용한 만큼만 비용 지불 (Pay-as-you-go)
- 초기 투자 비용 없음
- HTTP API는 REST API보다 70% 저렴
REST API 가격 (서울 리전):
- 처음 3억 3,300만 건: 건당 $0.00000381
- 다음 6억 6,700만 건: 건당 $0.00000318
- 10억 건 초과: 건당 $0.00000222
HTTP API 가격 (서울 리전):
- 처음 3억 건: 건당 $0.00000127
- 다음 7억 건: 건당 $0.00000095✅ 개발 생산성 향상
- SDK 자동 생성 (JavaScript, Python, Java 등)
- OpenAPI(Swagger) 사양 가져오기/내보내기
- AWS SAM, CDK, CloudFormation 지원
// AWS SDK 자동 생성 예시
import apigClientFactory from 'aws-api-gateway-client';
const apigClient = apigClientFactory.newClient({
invokeUrl: 'https://abc123.execute-api.ap-northeast-2.amazonaws.com',
apiKey: 'your-api-key'
});
// 타입 안전한 API 호출
const result = await apigClient.usersGet({}, {}, {});
console.log(result.data);✅ 모니터링 및 분석
- CloudWatch 메트릭 자동 수집
- X-Ray 통합으로 요청 추적
- 액세스 로그, 실행 로그
// X-Ray로 요청 추적
const AWSXRay = require('aws-xray-sdk-core');
const AWS = AWSXRay.captureAWS(require('aws-sdk'));
exports.handler = async (event) => {
// Lambda 함수 내부의 모든 AWS SDK 호출이 X-Ray에 추적됨
const dynamodb = new AWS.DynamoDB.DocumentClient();
const result = await dynamodb.get({
TableName: 'Users',
Key: { userId: event.userId }
}).promise();
return result.Item;
};✅ 다양한 통합 옵션
- Lambda 함수
- HTTP 엔드포인트 (온프레미스, 다른 클라우드)
- AWS 서비스 (DynamoDB, S3, SQS, SNS 등)
- VPC Link (프라이빗 리소스)
- Mock 응답
한계
⚠️ 콜드 스타트
- Lambda 통합 시 첫 요청에 지연 시간 발생 가능
- 해결 방법: Provisioned Concurrency 사용
// AWS CDK로 Provisioned Concurrency 설정
const version = lambda.addVersion({
provisionedConcurrentExecutions: 5 // 항상 5개 인스턴스 준비
});
const alias = new lambda.Alias(this, 'LiveAlias', {
aliasName: 'live',
version
});⚠️ 타임아웃 제한
- 통합 타임아웃: 최대 29초
- WebSocket 연결: 최대 2시간 (유휴 타임아웃 10분)
// 장시간 실행 작업은 비동기 처리
exports.handler = async (event) => {
if (event.httpMethod === 'POST') {
// SQS에 작업 추가 (비동기)
await sqs.sendMessage({
QueueUrl: process.env.QUEUE_URL,
MessageBody: JSON.stringify(event.body)
}).promise();
return {
statusCode: 202,
body: JSON.stringify({
message: '작업이 대기열에 추가되었습니다'
})
};
}
};⚠️ 페이로드 크기 제한
- 요청/응답 페이로드: 최대 10MB
- WebSocket 메시지: 최대 128KB
// 대용량 파일 업로드는 S3 Presigned URL 사용
const s3 = new AWS.S3();
exports.handler = async (event) => {
// Presigned URL 생성
const url = s3.getSignedUrl('putObject', {
Bucket: 'my-bucket',
Key: 'uploads/file.pdf',
Expires: 3600, // 1시간 유효
ContentType: 'application/pdf'
});
return {
statusCode: 200,
body: JSON.stringify({
uploadUrl: url,
message: '이 URL로 파일을 직접 업로드하세요'
})
};
};
// 클라이언트에서 Presigned URL로 업로드
async function uploadFile(file) {
// 1. API Gateway에서 Presigned URL 요청
const response = await fetch(`${API_URL}/upload-url`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
fileName: file.name,
fileType: file.type
})
});
const { uploadUrl } = await response.json();
// 2. Presigned URL로 직접 S3에 업로드
await fetch(uploadUrl, {
method: 'PUT',
headers: {
'Content-Type': file.type
},
body: file
});
console.log('파일 업로드 완료');
}⚠️ 학습 곡선
- 많은 기능과 옵션으로 인한 초기 학습 시간
- VTL(Velocity Template Language) 문법 익히기 필요
- IAM 정책 이해 필요
권장 접근 방법:
// 1단계: 간단한 Lambda 프록시 통합부터 시작
// API Gateway는 요청을 그대로 Lambda에 전달
const response = await fetch(`${API_URL}/simple`, {
method: 'GET'
});
// 2단계: 요청/응답 검증 추가
// API Gateway에서 스키마 검증 활성화
// 3단계: 매핑 템플릿 사용 (필요 시)
// VTL을 사용한 복잡한 변환트레이드오프
언제 사용하면 좋은가?
- 서버리스 아키텍처 구축 시
- 마이크로서비스 통합이 필요할 때
- 트래픽 변동이 큰 애플리케이션
- 빠른 프로토타이핑 및 MVP 개발
- 관리 부담을 최소화하고 싶을 때
언제 피해야 하는가?
- 매우 낮은 지연 시간이 중요한 경우 (< 10ms)
- 대용량 파일 전송이 주 목적인 경우
- 복잡한 상태 관리가 필요한 경우
- 온프레미스에서만 운영해야 하는 경우
관련 개념
유사 개념
1. Application Load Balancer (ALB)
공통점:
- HTTP(S) 트래픽 라우팅
- 경로 기반 라우팅
- HTTPS 지원
차이점:
[API Gateway]
- API 중심 기능 (인증, 요청 검증, SDK 생성)
- Lambda와 긴밀한 통합
- 사용량 계획 및 API 키 관리
[ALB]
- EC2 인스턴스, 컨테이너 로드 밸런싱
- 더 낮은 지연 시간
- WebSocket 장시간 연결 지원2. CloudFront (CDN)
공통점:
- 글로벌 엣지 로케이션
- HTTPS 지원
- 캐싱
차이점:
[API Gateway]
- API 관리 및 보안
- 백엔드 통합 (Lambda, HTTP 등)
- 요청/응답 변환
[CloudFront]
- 정적 콘텐츠 배포 최적화
- 동영상 스트리밍
- 더 많은 캐싱 옵션3. AWS AppSync (GraphQL)
공통점:
- 완전 관리형 API 서비스
- 실시간 데이터 지원
- AWS 서비스 통합
차이점:
[API Gateway]
- REST, HTTP, WebSocket API
- 더 유연한 통합 옵션
- 커스텀 비즈니스 로직
[AppSync]
- GraphQL 전용
- 오프라인 데이터 동기화
- 실시간 구독 내장대안
1. Kong API Gateway
- 장점: 오픈소스, 플러그인 생태계, 온프레미스 배포
- 단점: 자체 관리 필요, AWS 서비스와의 통합 제한
- 언제 사용: 멀티 클라우드, 복잡한 커스터마이징 필요 시
2. Nginx + Lua
- 장점: 높은 성능, 완전한 제어, 무료
- 단점: 모든 것을 직접 구현, 관리 부담
- 언제 사용: 특수한 요구사항, 온프레미스 환경
3. Express.js (커스텀 게이트웨이)
- 장점: 완전한 유연성, JavaScript 생태계
- 단점: 인프라 관리, 확장성 직접 구현
- 언제 사용: 소규모 프로젝트, 프로토타이핑
비교 예시:
// API Gateway + Lambda (관리형, 자동 확장)
exports.handler = async (event) => {
return {
statusCode: 200,
body: JSON.stringify({ message: 'Hello' })
};
};
// vs
// Express.js (자체 관리, 수동 확장)
const express = require('express');
const app = express();
app.get('/hello', (req, res) => {
res.json({ message: 'Hello' });
});
// 서버 실행, PM2로 프로세스 관리, Nginx 로드 밸런싱 등 필요
app.listen(3000);더 알아보기
심화 학습
고급 패턴:
- API Composition (여러 백엔드 호출 후 결합)
// Lambda 함수에서 여러 서비스 호출 후 결합
exports.handler = async (event) => {
const userId = event.pathParameters.id;
// 병렬로 여러 서비스 호출
const [userProfile, userOrders, userPayments] = await Promise.all([
getUserProfile(userId),
getUserOrders(userId),
getUserPayments(userId)
]);
// 결과 결합
return {
statusCode: 200,
body: JSON.stringify({
profile: userProfile,
orders: userOrders,
payments: userPayments
})
};
};
async function getUserProfile(userId) {
const result = await dynamodb.get({
TableName: 'Users',
Key: { userId }
}).promise();
return result.Item;
}
async function getUserOrders(userId) {
const result = await dynamodb.query({
TableName: 'Orders',
IndexName: 'UserIdIndex',
KeyConditionExpression: 'userId = :userId',
ExpressionAttributeValues: {
':userId': userId
}
}).promise();
return result.Items;
}
async function getUserPayments(userId) {
// 외부 결제 시스템 API 호출
const response = await fetch(`https://payment-api.com/users/${userId}/payments`);
return response.json();
}- Circuit Breaker 패턴
// Lambda 함수에서 Circuit Breaker 구현
class CircuitBreaker {
constructor(threshold = 5, timeout = 60000) {
this.failureCount = 0;
this.threshold = threshold;
this.timeout = timeout;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
this.nextAttempt = Date.now();
}
async call(fn) {
if (this.state === 'OPEN') {
if (Date.now() < this.nextAttempt) {
throw new Error('Circuit breaker is OPEN');
}
this.state = 'HALF_OPEN';
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failureCount = 0;
this.state = 'CLOSED';
}
onFailure() {
this.failureCount++;
if (this.failureCount >= this.threshold) {
this.state = 'OPEN';
this.nextAttempt = Date.now() + this.timeout;
}
}
}
// 사용
const breaker = new CircuitBreaker(5, 60000);
exports.handler = async (event) => {
try {
const result = await breaker.call(async () => {
// 외부 API 호출
const response = await fetch('https://external-api.com/data');
if (!response.ok) throw new Error('API 실패');
return response.json();
});
return {
statusCode: 200,
body: JSON.stringify(result)
};
} catch (error) {
// Circuit이 열려있으면 캐시된 데이터 반환
const cached = await getCachedData();
return {
statusCode: 200,
body: JSON.stringify({
data: cached,
fromCache: true
})
};
}
};- Request Deduplication (중복 요청 방지)
// Lambda 함수에서 Redis를 사용한 중복 요청 방지
const Redis = require('ioredis');
const crypto = require('crypto');
const redis = new Redis({
host: process.env.REDIS_HOST,
port: 6379
});
exports.handler = async (event) => {
// 요청 고유 ID 생성 (body 기반)
const requestId = crypto
.createHash('md5')
.update(JSON.stringify(event.body))
.digest('hex');
// Redis에서 이미 처리 중인지 확인
const existing = await redis.get(`request:${requestId}`);
if (existing) {
// 이미 처리 중이면 기다렸다가 결과 반환
await new Promise(resolve => setTimeout(resolve, 1000));
const result = await redis.get(`result:${requestId}`);
return {
statusCode: 200,
body: result,
headers: {
'X-Deduplicated': 'true'
}
};
}
// 처리 중 표시
await redis.setex(`request:${requestId}`, 60, 'processing');
try {
// 실제 비즈니스 로직 처리
const result = await processRequest(event.body);
// 결과 캐싱
await redis.setex(
`result:${requestId}`,
60,
JSON.stringify(result)
);
return {
statusCode: 200,
body: JSON.stringify(result)
};
} finally {
// 처리 중 플래그 제거
await redis.del(`request:${requestId}`);
}
};실습
튜토리얼:
실습 프로젝트 아이디어:
- 할 일 관리 API
// GET /todos - 할 일 목록 조회
// POST /todos - 새 할 일 추가
// PUT /todos/{id} - 할 일 수정
// DELETE /todos/{id} - 할 일 삭제
// PATCH /todos/{id}/complete - 할 일 완료 처리- 실시간 채팅 애플리케이션
- WebSocket API 사용
- DynamoDB로 메시지 저장
- S3로 파일 공유
- 마이크로서비스 API 게이트웨이
- 사용자 서비스, 주문 서비스, 결제 서비스 통합
- API Composition 패턴 적용
- Lambda Authorizer로 JWT 인증
관련 자료
공식 문서:
기술 블로그:
커뮤니티:
다음 단계
이제 AWS API Gateway의 개념과 동작 원리를 이해했다면:
-
실습으로 시작
- AWS Console에서 첫 REST API 생성
- Lambda 함수 하나와 연동해보기
- Postman이나 curl로 API 테스트
-
인증 추가
- API 키 생성 및 적용
- Lambda Authorizer로 커스텀 인증 구현
- Cognito User Pools 통합
-
고급 기능 탐색
- 캐싱 활성화 및 성능 측정
- 요청/응답 매핑 템플릿 작성
- CloudWatch 메트릭 분석
-
프로덕션 준비
- 사용량 계획 및 API 키 관리
- 스테이지 분리 (dev, staging, prod)
- 모니터링 및 알림 설정
- 비용 최적화
권장 학습 순서:
1. 간단한 Lambda 프록시 통합 (1-2시간)
↓
2. API 키 및 사용량 계획 (1시간)
↓
3. Lambda Authorizer 구현 (2-3시간)
↓
4. 요청/응답 검증 및 변환 (2-3시간)
↓
5. WebSocket API 구축 (3-4시간)
↓
6. 프로덕션 배포 및 모니터링 (2-3시간)행운을 빕니다! 🚀