왜 URL 단축 서비스를 만들까?
여러분이 마케팅 팀에서 일하고 있다고 상상해 보세요. 새 캠페인을 시작하면서 SNS에 링크를 올려야 합니다. 그런데 링크가 이렇게 생겼습니다:

https://www.example.com/products/2024-spring-sale/category?utm_source=instagram&utm_medium=story&utm_campaign=spring_launch_2024&ref=homepage_banner_v3
이 링크를 인스타그램 바이오에 넣으려니 너무 길고, SMS로 보내면 두 줄을 차지하고, QR 코드로 만들면 점이 빽빽해서 인식이 안 됩니다.
그래서 등장한 것이 URL 단축 서비스입니다. bit.ly, tinyurl 같은 서비스가 대표적이죠. https://short.link/abc123 처럼 짧은 주소로 변환하고, 누군가 그 주소를 클릭하면 원본 페이지로 자동 이동시킵니다.
이 서비스가 비즈니스에서 중요한 이유는 단순한 "URL 줄이기" 이상입니다:
- 클릭 추적: 언제, 어디서, 몇 명이 클릭했는지 데이터 수집
- A/B 테스트: 같은 페이지에 다른 단축 URL을 붙여 어떤 채널이 효과적인지 비교
- 브랜딩: 자체 도메인으로 단축 URL을 만들면 신뢰도 향상
- 만료 관리: 이벤트가 끝나면 자동으로 URL을 비활성화
오늘 이 실습에서는 bit.ly의 핵심 기능을 API Gateway + Lambda + DynamoDB로 직접 구현합니다. 서버 한 대 없이, 완전히 서버리스로 만들어 보겠습니다. 트래픽이 0이면 비용도 0이고, 갑자기 트래픽이 100배 늘어도 자동으로 처리됩니다.
실습을 시작하기 전에 AWS 콘솔에 로그인되어 있는지 확인하세요. 리전은 ap-northeast-2 (서울) 을 사용합니다.
아키텍처 개요

서비스 흐름
비용 예측
비용 계산기
1,000 요청당 $3.50
100만 요청당 $0.20 + 실행시간
쓰기 $1.25/백만, 읽기 $0.25/백만
* 실제 비용은 AWS 요금 정책에 따라 달라질 수 있습니다.
Step 1: DynamoDB 테이블 생성
DynamoDB는 URL 매핑 정보를 저장하는 데이터베이스 역할을 합니다. 각 단축 URL에 대해 원본 URL, 생성 시간, 클릭 수를 저장합니다.
- AWS 콘솔 상단 검색창에 DynamoDB를 입력하고 서비스를 클릭합니다
- 왼쪽 메뉴에서 테이블 클릭 후 오른쪽 상단의 테이블 생성 버튼을 클릭합니다
- 테이블 이름:
lab-urls입력 - 파티션 키:
shortId입력 후 타입은 문자열(S) 을 선택합니다 - 테이블 설정에서 설정 사용자 지정 을 선택하고, 읽기/쓰기 용량 모드를 온디맨드 로 변경합니다
- 나머지는 기본값 그대로 두고 테이블 생성 클릭
- 상태가 활성 으로 바뀔 때까지 약 10-20초 대기합니다
aws dynamodb create-table \
--table-name lab-urls \
--attribute-definitions AttributeName=shortId,AttributeType=S \
--key-schema AttributeName=shortId,KeyType=HASH \
--billing-mode PAY_PER_REQUEST테이블 항목에는 shortId, originalUrl, createdAt, clickCount 필드를
저장합니다. DynamoDB는 스키마리스이므로 테이블 생성 시에는 키만 정의하면 됩니다.
나머지 필드는 데이터를 넣을 때 자유롭게 추가할 수 있습니다.
Step 2: URL 생성 Lambda 함수
이 Lambda 함수는 원본 URL을 받아서 8자리 단축 코드를 생성하고 DynamoDB에 저장합니다.
- AWS 콘솔 상단 검색창에 Lambda를 입력하고 서비스 클릭
- 함수 생성 버튼 클릭 → 새로 작성 선택
- 함수 이름:
lab-url-create입력 - 런타임: Node.js 20.x 선택 (Python 3.12도 가능하지만 이 실습에서는 Node.js 사용)
- 기본 실행 역할 변경 펼치기 → 기본 Lambda 권한을 가진 새 역할 생성 선택
- 함수 생성 클릭
- 함수가 생성되면 구성 탭 → 권한 → 역할 이름 클릭 → IAM 콘솔로 이동
- 권한 추가 → 정책 연결 →
AmazonDynamoDBFullAccess검색 후 연결 - Lambda 콘솔로 돌아와 구성 탭 → 환경 변수 →
TABLE_NAME=lab-urls추가 - 코드 탭에서 아래 코드를 붙여넣고 Deploy 클릭
aws lambda create-function \
--function-name lab-url-create \
--runtime nodejs20.x \
--role arn:aws:iam::ACCOUNT_ID:role/lambda-execution-role \
--handler index.handler \
--zip-file fileb://url-create.zip아래는 URL 생성 Lambda의 전체 코드입니다. Lambda 콘솔의 코드 편집기에 붙여넣으세요.
// createUrl Lambda — URL 생성 함수 (Node.js 20.x 런타임)
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { PutCommand, DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
import crypto from 'crypto';
const client = DynamoDBDocumentClient.from(new DynamoDBClient({}));
const TABLE = process.env.TABLE_NAME || 'lab-urls';
export const handler = async (event) => {
const { url } = JSON.parse(event.body);
const shortCode = crypto.createHash('sha256')
.update(url + Date.now()).digest('hex').slice(0, 8);
await client.send(new PutCommand({
TableName: TABLE,
Item: {
shortId: shortCode,
originalUrl: url,
clickCount: 0,
createdAt: new Date().toISOString(),
ttl: Math.floor(Date.now() / 1000) + 86400 * 30, // 30일 후 자동 삭제
},
}));
return {
statusCode: 201,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
shortId: shortCode,
shortUrl: `https://${event.headers.Host}/${shortCode}`,
}),
};
};TABLE_NAME 환경 변수를 Lambda 설정에서 lab-urls로 지정하세요.
SHA-256 해시의 앞 8자리를 단축 코드로 사용하며, TTL을 설정하여
30일 후 DynamoDB에서 자동 삭제됩니다.
자주 하는 실수: Lambda 함수를 만든 후 코드를 수정했는데 Deploy 버튼을 누르지 않는 경우가 많습니다. 코드 편집기 상단에 "배포되지 않은 변경 사항이 있습니다"라는 주황색 배너가 보이면 반드시 Deploy를 클릭하세요.
Step 3: 리다이렉트 Lambda 함수
이 함수는 단축 URL로 접속한 사용자를 원본 URL로 리다이렉트합니다. 동시에 클릭 수도 증가시킵니다.
- 1Lambda 콘솔 → 함수 생성 → 이름: lab-url-redirect, 런타임: Node.js 20.x
- 2Step 2와 같은 방식으로 IAM 역할에 AmazonDynamoDBFullAccess 정책을 연결합니다
- 3환경 변수에 TABLE_NAME = lab-urls 추가
- 4코드 작성: pathParameters에서 shortId 추출
- 5DynamoDB에서 shortId로 원본 URL 조회 (GetCommand 사용)
- 6clickCount를 1 증가 (UpdateCommand의 ADD 연산 사용 — 원자적 증가)
- 7HTTP 301 응답 헤더에 Location: 원본URL 설정하여 브라우저가 자동 리다이렉트하도록 합니다
- 8URL을 찾지 못하면 404 응답 반환
아래는 리다이렉트 Lambda의 전체 코드입니다.
// redirectUrl Lambda — 리다이렉트 함수 (Node.js 20.x 런타임)
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { GetCommand, UpdateCommand, DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
const client = DynamoDBDocumentClient.from(new DynamoDBClient({}));
const TABLE = process.env.TABLE_NAME || 'lab-urls';
export const handler = async (event) => {
const shortCode = event.pathParameters.shortId;
const { Item } = await client.send(new GetCommand({
TableName: TABLE,
Key: { shortId: shortCode },
}));
if (!Item) {
return { statusCode: 404, body: JSON.stringify({ error: 'URL을 찾을 수 없습니다' }) };
}
// Atomic counter — 클릭 수 원자적 증가
await client.send(new UpdateCommand({
TableName: TABLE,
Key: { shortId: shortCode },
UpdateExpression: 'ADD clickCount :inc',
ExpressionAttributeValues: { ':inc': 1 },
}));
return {
statusCode: 301,
headers: { Location: Item.originalUrl },
};
};UpdateExpression의 ADD 연산은 동시 요청에서도 안전하게 카운터를 증가시킵니다.
GetItem 후 PutItem으로 덮어쓰는 방식은 동시성 문제가 발생할 수 있으므로
반드시 UpdateCommand를 사용하세요.
Step 4: API Gateway 엔드포인트 구성
API Gateway는 외부 클라이언트(브라우저, curl 등)와 Lambda 함수 사이의 "현관문" 역할을 합니다.
- AWS 콘솔 상단에서 API Gateway 검색 후 클릭
- REST API 카드에서 빌드 클릭 (HTTP API가 아닌 REST API를 선택하세요)
- 새 API 선택, API 이름:
lab-url-shortener, 엔드포인트 유형: 리전 선택 후 API 생성 클릭 - 리소스 생성: 리소스 이름
shorten, 리소스 경로/shorten→ 리소스 생성 클릭 /shorten선택 → 메서드 생성 → POST → 통합 유형: Lambda 함수 →lab-url-create선택 → 메서드 생성 클릭- 루트(
/)로 돌아가서 리소스 생성 → 리소스 이름:shortId, 리소스 경로에{shortId}입력 (중괄호 포함) → 리소스 생성 클릭 /{shortId}선택 → 메서드 생성 → GET → 통합 유형: Lambda 함수 →lab-url-redirect선택 → 메서드 생성 클릭- GET 메서드의 메서드 응답에서 응답 추가 → HTTP 상태:
301→Location헤더 추가 - API 배포 클릭 → 새 스테이지 → 스테이지 이름:
prod→ 배포 - 화면 상단에 표시되는 호출 URL을 복사하세요 (예:
https://abc123.execute-api.ap-northeast-2.amazonaws.com/prod)
# REST API 생성
aws apigateway create-rest-api \
--name lab-url-shortener \
--endpoint-configuration types=REGIONAL
# 리소스 및 메서드는 콘솔에서 구성을 권장합니다Step 5: 리다이렉트 및 클릭 카운트 테스트
이제 완성된 서비스를 테스트해 봅시다. 터미널(명령 프롬프트)을 열어 아래 명령을 실행합니다.
- 1URL 단축 테스트 — 아래 curl 명령으로 URL을 단축합니다 (API_URL을 복사한 호출 URL로 교체): curl -X POST https://YOUR_API_URL/prod/shorten \ -H "Content-Type: application/json" \ -d '{"url": "https://aws.amazon.com/ko/lambda/"}'
- 2반환된 JSON에서 shortUrl 또는 shortId 값을 확인합니다
- 3리다이렉트 테스트 — 브라우저 주소창에 https://YOUR_API_URL/prod/{반환된shortId}를 입력하면 원본 페이지로 이동합니다
- 4클릭 카운트 확인 — DynamoDB 콘솔 → lab-urls 테이블 → 항목 탐색에서 해당 항목의 clickCount 값을 확인합니다
- 5여러 번 접속 후 clickCount가 정확히 증가하는지 확인합니다
- 6404 테스트 — 존재하지 않는 shortId로 접속하여 {"error": "URL을 찾을 수 없습니다"} 응답을 확인합니다
curl -v 옵션을 사용하면 301 응답 헤더의 Location 값을
확인할 수 있습니다. 브라우저는 자동으로 리다이렉트를 따라갑니다.
curl -L을 사용하면 리다이렉트를 자동으로 따라가서 최종 페이지 HTML을 받습니다.
트러블슈팅
"Internal Server Error" (500 에러)가 나오면: Lambda 함수의 CloudWatch Logs를 확인하세요. Lambda 콘솔 → 함수 선택 → 모니터링 탭 → CloudWatch Logs 보기 클릭. 가장 흔한 원인은 DynamoDB 테이블 이름 불일치 또는 IAM 권한 부족입니다.
"Missing Authentication Token" 에러가 나오면:
API Gateway에서 리소스 경로나 메서드를 잘못 설정한 경우입니다. /shorten에 POST 메서드가, /{shortId}에 GET 메서드가 올바르게 연결되어 있는지 확인하세요.
또한 API를 배포했는지 확인하세요 — 변경 후 재배포하지 않으면 이전 버전이 동작합니다.
리다이렉트가 안 되고 JSON 응답만 나오면:
API Gateway 메서드 응답에 301 상태 코드와 Location 헤더가 추가되어 있는지 확인하세요.
Lambda 프록시 통합을 사용하는 경우 Lambda에서 직접 statusCode: 301과 headers: { Location: url }을 반환해야 합니다.
"AccessDeniedException" 에러가 나오면:
Lambda 실행 역할에 AmazonDynamoDBFullAccess 정책이 연결되어 있는지 확인하세요.
IAM 콘솔 → 역할 → Lambda 실행 역할 선택 → 권한 탭에서 정책 목록을 확인합니다.
핵심 개념 확인
확장 아이디어
기본 서비스가 완성되었으니, 더 발전시켜 보세요:
- 커스텀 단축 코드: 사용자가 원하는 문자열(예:
my-link)을 직접 지정할 수 있도록 POST 요청에customCode필드 추가 - 만료 시간 설정: DynamoDB TTL 기능을 활용하여 1시간, 1일, 7일 등 원하는 기간 후 자동 삭제
- 클릭 통계 API: GET
/stats/{shortId}엔드포인트를 추가하여 클릭 수, 생성일, 마지막 접속일 등 반환 - 중복 URL 방지: 같은 원본 URL이 이미 등록되어 있으면 기존 단축 코드를 반환하도록 GSI(Global Secondary Index) 활용
- QR 코드 생성: 단축 URL 생성 시 QR 코드 이미지도 함께 생성하여 S3에 저장
학습 정리
핵심 치트시트
리소스 정리
실습 완료 후 반드시 아래 순서대로 리소스를 정리하여 불필요한 과금을 방지하세요.
- API Gateway → API 삭제 (
lab-url-shortener) - Lambda 함수 삭제 (
lab-url-create,lab-url-redirect) - DynamoDB 테이블 삭제 (
lab-urls) - IAM 역할 정리 (Lambda 실행 역할)
- CloudWatch 로그 그룹 삭제