2015년처럼 API를 작성하지 마세요
I’m happy to translate the article for you, but I need the actual text you’d like translated. Could you please paste the content (or the portion you want translated) here? I’ll keep the source line and all formatting exactly as you specify.
소개
우리는 2025년에 살고 있으며, 많은 코드베이스가 여전히 API를 단순히 “JSON을 반환하는 엔드포인트”로만 취급합니다. API 설계가 기본 CRUD 라우트를 넘어 발전하지 않았다면, 성능, 확장성, 그리고 개발자 경험을 희생하고 있는 것입니다.
1. 기본적으로 모든 데이터를 반환하지 않기
문제점
// 2015 mindset
app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
res.json(user); // Returns 47 fields no one asked for
});
왜 안 좋은가
- 모바일 성능 저하
- 내부 데이터 구조 노출
- 프런트엔드가 직접 필터링해야 함
- 캐싱이 거의 불가능
현대적인 접근법
app.get('/api/users/:id', async (req, res) => {
const fields = req.query.fields?.split(',') || ['id', 'name', 'email'];
const user = await db.users.findById(req.params.id, { select: fields });
res.json(user);
});
클라이언트가 필요한 것만 요청하도록 하세요. GraphQL이 몇 년 전부터 우리에게 가르쳐줬듯—REST도 동일하게 구현할 수 있습니다.
2. 제한 없는 페이지네이션은 범죄다
The Problem
app.get('/api/products', async (req, res) => {
const products = await db.products.find(); // All 50,000 rows
res.json(products);
});
Why It’s Bad
- 하나의 요청으로 데이터베이스가 마비될 수 있음
- 성장에 대응할 방법이 없음
- 타임아웃이 발생하면 사용자 경험이 악화됨
Modern Approach (Cursor‑based Pagination)
app.get('/api/products', async (req, res) => {
const limit = Math.min(parseInt(req.query.limit) || 20, 100);
const cursor = req.query.cursor;
const products = await db.products.find({
where: cursor ? { id: { gt: cursor } } : {},
limit: limit + 1
});
const hasNext = products.length > limit;
const items = hasNext ? products.slice(0, -1) : products;
res.json({
data: items,
cursor: hasNext ? items[items.length - 1].id : null
});
});
예측 가능한 부하와 서버를 파괴하지 않는 무한 스크롤.
3. 오류 응답은 사후 고려 사항이 아니다
문제점
app.post('/api/orders', async (req, res) => {
try {
const order = await createOrder(req.body);
res.json(order);
} catch (err) {
res.status(500).json({ error: 'Something went wrong' });
}
});
프론트엔드 개발자들은 이 모호한 응답을 싫어합니다.
현대적 접근법
app.post('/api/orders', async (req, res) => {
try {
const order = await createOrder(req.body);
res.json({ data: order });
} catch (err) {
if (err.name === 'ValidationError') {
return res.status(400).json({
error: {
code: 'VALIDATION_FAILED',
message: 'Invalid order data',
fields: err.details
}
});
}
if (err.name === 'InsufficientStock') {
return res.status(409).json({
error: {
code: 'INSUFFICIENT_STOCK',
message: 'Product out of stock',
productId: err.productId
}
});
}
// Log actual error server‑side
logger.error(err);
res.status(500).json({
error: {
code: 'INTERNAL_ERROR',
message: 'Failed to create order'
}
});
}
});
구조화된 오류는 실행 가능한 피드백을 제공하여 프론트엔드가 실패를 우아하게 처리할 수 있게 합니다.
4. 더 이상 선택 사항이 아닌 속도 제한
문제
속도 제한이 없으면 API가 남용, 우발적인 무한 스크립트 실행, 그리고 비용이 많이 드는 급증에 취약해집니다.
현대적 접근법
import rateLimit from 'express-rate-limit';
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100,
standardHeaders: true,
legacyHeaders: false,
handler: (req, res) => {
res.status(429).json({
error: {
code: 'RATE_LIMIT_EXCEEDED',
message: 'Too many requests',
retryAfter: req.rateLimit.resetTime
}
});
}
});
app.use('/api/', limiter);
인프라를 보호하세요—2025년에는 이것이 기본 설정이어야 합니다.
5. 처음부터 버전 관리
문제
app.get('/api/users', …); – 계약이 변경되면 어떻게 될까요?
현대적 접근법
app.get('/api/v1/users', …); // 기존 클라이언트
app.get('/api/v2/users', …); // 새로운 동작
버전 관리는 조기 최적화가 아니라, 사용자와 미래의 자신을 배려하는 것입니다.
6. HTTP 상태 코드를 무시하지 말자
문제
res.status(200).json({ error: 'User not found' }); // WRONG
현대적 접근법 – 적절한 코드를 사용하세요:
- 200 – 성공
- 201 – 생성됨
- 400 – 잘못된 요청 (클라이언트 오류)
- 401 – 인증되지 않음
- 403 – 금지됨
- 404 – 찾을 수 없음
- 409 – 충돌 (중복, 제약 위반)
- 422 – 처리할 수 없는 엔터티 (검증 실패)
- 429 – 속도 제한
- 500 – 서버 오류 (당신의 실수)
올바른 상태 코드는 클라이언트, 모니터링 도구 및 캐시 계층이 제대로 작동하도록 합니다.
7. 캐싱 헤더는 무료 성능
문제
데이터가 몇 주 동안 변경되지 않았더라도 모든 요청이 데이터베이스에 도달합니다.
현대적 접근법
app.get('/api/products/:id', async (req, res) => {
const product = await db.products.findById(req.params.id);
res.set({
'Cache-Control': 'public, max-age=300', // 5 minutes
'ETag': generateETag(product)
});
res.json(product);
});
CDN, 브라우저 및 프록시가 무거운 작업을 대신 처리합니다.
레거시 API 패턴의 실제 비용
- 최적화되지 않은 단일 엔드포인트만으로도 데이터베이스 읽기 비용이 $800/월이 될 수 있습니다.
- 속도 제한을 놓쳐서 버그가 있는 모바일 앱이 우연히 DDoS를 일으킨 적이 있습니다.
- 부실한 오류 처리 때문에 모든 버그 보고서가 “무언가 잘못되었습니다”로 변했습니다.
현대 API 설계는 트렌드를 따르는 것이 아니라, 확장 가능하고 사용자의 실수를 견디며 2년 안에 완전한 재작성 없이도 지속될 수 있는 시스템을 구축하는 것입니다.
2025년에 아직도 보고 있는 구식 API 패턴은 무엇인가요? 댓글에 남겨 주세요.