Sessions
입문프리티어2-3시간

서버리스 REST API 구축

AWS Lambda, DynamoDB, API Gateway, Route 53을 활용하여 서버 없이 확장 가능한 REST API를 구축합니다.

What You Will Build
  • DynamoDB 테이블 (NoSQL 데이터베이스)
  • Lambda 함수 (CRUD 로직)
  • API Gateway (REST 엔드포인트)
  • 커스텀 도메인 연결 (선택)
  • 프론트엔드 연동 예제
AWS 프리티어 범위

Lambda

월 100만 요청 무료

DynamoDB

25GB 스토리지, 월 2억 요청 무료

API Gateway

월 100만 요청 무료 (12개월)

Route 53

호스팅 영역당 $0.50/월 (선택)

1
아키텍처 이해
서버리스 API 아키텍처를 이해합니다.

전체 아키텍처

이 워크샵에서 구축할 서버리스 API 아키텍처입니다.

┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Client    │────▶│  Route 53   │────▶│ API Gateway │────▶│   Lambda    │
│  (Browser)  │     │  (Domain)   │     │  (REST API) │     │  (Function) │
└─────────────┘     └─────────────┘     └─────────────┘     └──────┬──────┘
                                                                   │
                                                                   ▼
                                                            ┌─────────────┐
                                                            │  DynamoDB   │
                                                            │   (Table)   │
                                                            └─────────────┘

# 비용 (프리티어 기준):
# - Lambda: 월 100만 요청 무료
# - DynamoDB: 25GB 스토리지, 월 2억 요청 무료
# - API Gateway: 월 100만 요청 무료
# - Route 53: 호스팅 영역당 $0.50/월 (선택사항)

사전 준비

워크샵을 시작하기 전에 필요한 것들입니다.

# 필수 준비물:
# 1. AWS 계정 (프리티어 가능)
# 2. AWS CLI 설치 및 설정
# 3. Node.js 18+ 설치

# AWS CLI 설치 확인
aws --version

# AWS 자격 증명 설정
aws configure
# AWS Access Key ID: [YOUR_ACCESS_KEY]
# AWS Secret Access Key: [YOUR_SECRET_KEY]
# Default region name: ap-northeast-2
# Default output format: json
2
DynamoDB 테이블 생성
데이터를 저장할 DynamoDB 테이블을 생성합니다.

AWS Console에서 테이블 생성

DynamoDB 콘솔에서 테이블을 생성합니다.

# 1. AWS Console > DynamoDB > Tables > Create table

# 테이블 설정:
# Table name: TodoItems
# Partition key: id (String)

# Table settings: Default settings (On-demand 선택 권장)

# Create table 클릭

AWS CLI로 테이블 생성 (대안)

CLI를 사용하여 테이블을 생성할 수도 있습니다.

aws dynamodb create-table \
  --table-name TodoItems \
  --attribute-definitions AttributeName=id,AttributeType=S \
  --key-schema AttributeName=id,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST \
  --region ap-northeast-2

# 테이블 생성 확인
aws dynamodb describe-table --table-name TodoItems

테스트 데이터 삽입

테이블이 정상적으로 작동하는지 테스트합니다.

aws dynamodb put-item \
  --table-name TodoItems \
  --item '{
    "id": {"S": "1"},
    "title": {"S": "Learn AWS Lambda"},
    "completed": {"BOOL": false},
    "createdAt": {"S": "2024-01-01T00:00:00Z"}
  }'

# 데이터 조회
aws dynamodb scan --table-name TodoItems
3
Lambda 함수 생성
API 로직을 처리할 Lambda 함수를 작성합니다.

IAM 역할 생성

Lambda가 DynamoDB에 접근할 수 있는 IAM 역할을 생성합니다.

# 1. AWS Console > IAM > Roles > Create role

# Trusted entity type: AWS service
# Use case: Lambda

# Permissions policies 추가:
# - AWSLambdaBasicExecutionRole (CloudWatch Logs 권한)
# - AmazonDynamoDBFullAccess (DynamoDB 권한)

# Role name: TodoApiLambdaRole
# Create role 클릭

Lambda 함수 코드 작성

로컬에서 Lambda 함수 코드를 작성합니다.

// index.mjs
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import {
  DynamoDBDocumentClient,
  GetCommand,
  PutCommand,
  ScanCommand,
  DeleteCommand,
} from "@aws-sdk/lib-dynamodb";
import { randomUUID } from "crypto";

const client = new DynamoDBClient({ region: "ap-northeast-2" });
const docClient = DynamoDBDocumentClient.from(client);
const TABLE_NAME = "TodoItems";

export const handler = async (event) => {
  const { httpMethod, pathParameters, body } = event;
  
  const headers = {
    "Content-Type": "application/json",
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "GET,POST,PUT,DELETE,OPTIONS",
    "Access-Control-Allow-Headers": "Content-Type",
  };

  try {
    let response;

    switch (httpMethod) {
      case "GET":
        if (pathParameters?.id) {
          response = await getItem(pathParameters.id);
        } else {
          response = await getAllItems();
        }
        break;
      case "POST":
        response = await createItem(JSON.parse(body));
        break;
      case "PUT":
        response = await updateItem(pathParameters.id, JSON.parse(body));
        break;
      case "DELETE":
        response = await deleteItem(pathParameters.id);
        break;
      case "OPTIONS":
        return { statusCode: 200, headers, body: "" };
      default:
        return {
          statusCode: 400,
          headers,
          body: JSON.stringify({ error: "Unsupported method" }),
        };
    }

    return {
      statusCode: 200,
      headers,
      body: JSON.stringify(response),
    };
  } catch (error) {
    return {
      statusCode: 500,
      headers,
      body: JSON.stringify({ error: error.message }),
    };
  }
};

async function getAllItems() {
  const command = new ScanCommand({ TableName: TABLE_NAME });
  const result = await docClient.send(command);
  return result.Items;
}

async function getItem(id) {
  const command = new GetCommand({
    TableName: TABLE_NAME,
    Key: { id },
  });
  const result = await docClient.send(command);
  return result.Item;
}

async function createItem(data) {
  const item = {
    id: randomUUID(),
    ...data,
    createdAt: new Date().toISOString(),
  };
  const command = new PutCommand({
    TableName: TABLE_NAME,
    Item: item,
  });
  await docClient.send(command);
  return item;
}

async function updateItem(id, data) {
  const item = { id, ...data, updatedAt: new Date().toISOString() };
  const command = new PutCommand({
    TableName: TABLE_NAME,
    Item: item,
  });
  await docClient.send(command);
  return item;
}

async function deleteItem(id) {
  const command = new DeleteCommand({
    TableName: TABLE_NAME,
    Key: { id },
  });
  await docClient.send(command);
  return { deleted: true };
}

Lambda 함수 배포

AWS Console에서 Lambda 함수를 생성하고 코드를 배포합니다.

# 1. AWS Console > Lambda > Create function

# Function name: TodoApiFunction
# Runtime: Node.js 20.x
# Architecture: arm64 (비용 절감)
# Execution role: Use an existing role > TodoApiLambdaRole

# Create function 클릭

# 2. Code source에 위의 코드를 붙여넣기
# 3. Deploy 클릭

# 또는 CLI로 배포:
# zip -r function.zip index.mjs
# aws lambda create-function \
#   --function-name TodoApiFunction \
#   --runtime nodejs20.x \
#   --handler index.handler \
#   --role arn:aws:iam::YOUR_ACCOUNT_ID:role/TodoApiLambdaRole \
#   --zip-file fileb://function.zip

Lambda 테스트

Lambda 함수가 정상 작동하는지 테스트합니다.

# AWS Console > Lambda > TodoApiFunction > Test

# Test event 생성:
{
  "httpMethod": "GET",
  "pathParameters": null,
  "body": null
}

# Test 클릭하여 결과 확인
4
API Gateway 설정
REST API 엔드포인트를 생성합니다.

REST API 생성

API Gateway에서 새 REST API를 생성합니다.

# 1. AWS Console > API Gateway > Create API
# 2. REST API > Build 선택

# API details:
# API name: TodoApi
# API endpoint type: Regional

# Create API 클릭

리소스 및 메서드 생성

API 리소스와 HTTP 메서드를 설정합니다.

# /todos 리소스 생성
# 1. Resources > Create resource
# Resource name: todos
# Resource path: /todos
# CORS 활성화 체크
# Create resource 클릭

# GET /todos 메서드 생성
# 1. /todos 선택 > Create method
# Method type: GET
# Integration type: Lambda Function
# Lambda function: TodoApiFunction
# Create method 클릭

# POST /todos 메서드 생성
# 동일하게 POST 메서드 추가

# /todos/{id} 리소스 생성
# 1. /todos 선택 > Create resource
# Resource name: {id}
# Resource path: {id}
# Create resource 클릭

# GET, PUT, DELETE /todos/{id} 메서드 생성
# 각각 Lambda 함수와 연결

CORS 설정

브라우저에서 API를 호출할 수 있도록 CORS를 설정합니다.

# 각 리소스에서 CORS 활성화
# 1. /todos 선택 > Enable CORS
# 2. Access-Control-Allow-Origin: *
# 3. Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS
# 4. Save 클릭

# /todos/{id}에도 동일하게 CORS 설정

API 배포

API를 스테이지에 배포합니다.

# 1. Deploy API 클릭
# 2. Stage: *New Stage*
# Stage name: prod
# Deploy 클릭

# 배포 완료 후 Invoke URL 확인:
# https://xxxxxxxxxx.execute-api.ap-northeast-2.amazonaws.com/prod

API 테스트

배포된 API를 테스트합니다.

# 전체 조회
curl https://YOUR_API_ID.execute-api.ap-northeast-2.amazonaws.com/prod/todos

# 새 항목 생성
curl -X POST \
  https://YOUR_API_ID.execute-api.ap-northeast-2.amazonaws.com/prod/todos \
  -H "Content-Type: application/json" \
  -d '{"title": "Learn Serverless", "completed": false}'

# 특정 항목 조회
curl https://YOUR_API_ID.execute-api.ap-northeast-2.amazonaws.com/prod/todos/ITEM_ID

# 항목 수정
curl -X PUT \
  https://YOUR_API_ID.execute-api.ap-northeast-2.amazonaws.com/prod/todos/ITEM_ID \
  -H "Content-Type: application/json" \
  -d '{"title": "Learn Serverless", "completed": true}'

# 항목 삭제
curl -X DELETE \
  https://YOUR_API_ID.execute-api.ap-northeast-2.amazonaws.com/prod/todos/ITEM_ID
5
Route 53 커스텀 도메인 (선택)
API에 커스텀 도메인을 연결합니다.

ACM 인증서 발급

커스텀 도메인용 SSL 인증서를 발급받습니다.

# 1. AWS Console > Certificate Manager (us-east-1 리전!)
# 2. Request certificate > Request a public certificate

# Domain names: api.yourdomain.com
# Validation method: DNS validation

# Request 클릭

# 3. 인증서 상세 > Create records in Route 53
# (Route 53에서 도메인을 관리하는 경우 자동 검증)

API Gateway 커스텀 도메인 생성

API Gateway에서 커스텀 도메인을 설정합니다.

# 1. API Gateway > Custom domain names > Create

# Domain name: api.yourdomain.com
# API endpoint type: Regional
# ACM certificate: 위에서 발급받은 인증서 선택

# Create domain name 클릭

# 2. API mappings 탭 > Configure API mappings
# API: TodoApi
# Stage: prod
# Path: (비워두기)

# Save 클릭

Route 53 레코드 생성

Route 53에서 API Gateway를 가리키는 레코드를 생성합니다.

# 1. Route 53 > Hosted zones > yourdomain.com

# 2. Create record
# Record name: api
# Record type: A
# Alias: Yes
# Route traffic to: 
#   - Alias to API Gateway API
#   - Region: ap-northeast-2
#   - API Gateway domain name 선택

# Create records 클릭

# 3. 전파 대기 (최대 몇 분 소요)

# 4. 테스트
curl https://api.yourdomain.com/todos
6
프론트엔드 연동
React/Next.js에서 API를 호출하는 방법입니다.

API 호출 예제

프론트엔드에서 서버리스 API를 호출합니다.

// lib/api.ts
const API_URL = "https://YOUR_API_ID.execute-api.ap-northeast-2.amazonaws.com/prod";

export async function getTodos() {
  const res = await fetch(`${API_URL}/todos`);
  return res.json();
}

export async function createTodo(title: string) {
  const res = await fetch(`${API_URL}/todos`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ title, completed: false }),
  });
  return res.json();
}

export async function updateTodo(id: string, data: any) {
  const res = await fetch(`${API_URL}/todos/${id}`, {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(data),
  });
  return res.json();
}

export async function deleteTodo(id: string) {
  const res = await fetch(`${API_URL}/todos/${id}`, {
    method: "DELETE",
  });
  return res.json();
}

React 컴포넌트 예제

Todo 리스트 컴포넌트 예제입니다.

// components/TodoList.tsx
"use client";
import { useEffect, useState } from "react";
import { getTodos, createTodo, deleteTodo } from "@/lib/api";

export default function TodoList() {
  const [todos, setTodos] = useState([]);
  const [newTitle, setNewTitle] = useState("");

  useEffect(() => {
    loadTodos();
  }, []);

  async function loadTodos() {
    const data = await getTodos();
    setTodos(data);
  }

  async function handleCreate() {
    if (!newTitle.trim()) return;
    await createTodo(newTitle);
    setNewTitle("");
    loadTodos();
  }

  async function handleDelete(id: string) {
    await deleteTodo(id);
    loadTodos();
  }

  return (
    <div>
      <div className="flex gap-2 mb-4">
        <input
          value={newTitle}
          onChange={(e) => setNewTitle(e.target.value)}
          placeholder="New todo..."
          className="border px-3 py-2 rounded"
        />
        <button onClick={handleCreate} className="bg-blue-500 text-white px-4 py-2 rounded">
          Add
        </button>
      </div>
      <ul className="space-y-2">
        {todos.map((todo: any) => (
          <li key={todo.id} className="flex justify-between items-center border p-3 rounded">
            <span>{todo.title}</span>
            <button onClick={() => handleDelete(todo.id)} className="text-red-500">
              Delete
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}
7
리소스 정리
워크샵 완료 후 리소스를 정리합니다.

리소스 삭제 순서

비용 발생을 방지하기 위해 리소스를 삭제합니다.

# 1. API Gateway 삭제
# API Gateway > APIs > TodoApi > Delete

# 2. Lambda 함수 삭제
# Lambda > Functions > TodoApiFunction > Delete

# 3. DynamoDB 테이블 삭제
# DynamoDB > Tables > TodoItems > Delete

# 4. IAM 역할 삭제
# IAM > Roles > TodoApiLambdaRole > Delete

# 5. (선택) Route 53 레코드 삭제
# Route 53 > Hosted zones > 레코드 삭제

# 6. (선택) ACM 인증서 삭제
# Certificate Manager > 인증서 삭제

# 7. (선택) API Gateway 커스텀 도메인 삭제
# API Gateway > Custom domain names > 삭제
Congratulations!

축하합니다! 서버리스 REST API가 완성되었습니다. 이제 서버 관리 없이 자동으로 확장되는 API를 운영할 수 있습니다.

Next Steps:

  • • API 인증 추가 (Cognito, API Key)
  • • Lambda Layer로 공통 코드 분리
  • • CloudWatch로 모니터링 설정
  • • SAM/Serverless Framework로 IaC 전환
  • • CI/CD 파이프라인 구축