RESTful API란 무엇이며 주요 설계 원칙은 무엇인가요?
힌트
무상태성, 자원 중심 설계, HTTP 메서드 활용을 생각해보세요.
정답 및 해설
RESTful API란 무엇이며 주요 설계 원칙은 무엇인가요?
REST(Representational State Transfer)는 2000년 로이 필딩(Roy Fielding)이 박사 논문에서 제안한 분산 하이퍼미디어 시스템을 위한 아키텍처 스타일입니다. REST의 원칙을 따르는 API를 RESTful API라고 합니다. 특정 기술이나 프로토콜이 아닌 설계 원칙의 집합이며, 일반적으로 HTTP 위에서 구현됩니다.
REST의 6가지 아키텍처 원칙
1. 클라이언트-서버 분리 (Client-Server)
클라이언트와 서버는 명확히 분리되어야 합니다. 클라이언트는 UI/UX를, 서버는 비즈니스 로직과 데이터 저장을 담당합니다. 이 분리로 독립적인 개발과 배포가 가능해집니다.
클라이언트(React, iOS, Android 앱)
↕ HTTP 요청/응답
서버(Node.js, Django, Spring 등)
↕ SQL/NoSQL 쿼리
데이터베이스
2. 무상태성 (Stateless)
각 요청은 독립적이어야 합니다. 서버는 이전 요청의 상태를 저장하지 않으며, 각 요청에는 필요한 모든 정보(인증 토큰 등)가 포함되어야 합니다.
-- 올바른 방식: 매 요청마다 인증 정보 포함 --
GET /api/users/me HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
-- 잘못된 방식: 서버 세션에 의존 --
GET /api/users/me HTTP/1.1
Cookie: session_id=abc123 (서버가 세션 상태 저장)
3. 캐시 가능 (Cacheable)
응답 데이터는 캐시 가능 여부를 명시해야 합니다. 캐시를 통해 성능을 향상시킵니다.
HTTP/1.1 200 OK
Cache-Control: max-age=3600 -- 1시간 캐시 허용
ETag: "v1.0.3-abc123" -- 조건부 요청을 위한 식별자
-- 이후 요청에서 조건부 확인 --
GET /api/products HTTP/1.1
If-None-Match: "v1.0.3-abc123"
-- 변경 없으면 304 Not Modified 반환 (데이터 전송 없음)
4. 계층화 시스템 (Layered System)
클라이언트는 직접 최종 서버에 연결하는지, 중간 서버(프록시, 로드밸런서, CDN)를 거치는지 알 필요가 없습니다.
클라이언트
↓
CDN (정적 자원)
↓
로드 밸런서
↓
API 서버 1 / API 서버 2 / API 서버 3
↓
데이터베이스
5. 균일한 인터페이스 (Uniform Interface)
REST의 핵심 원칙으로, 일관된 방식으로 자원에 접근합니다. 4가지 제약이 있습니다.
- 자원 식별: URI로 자원을 식별
- 표현을 통한 자원 조작: JSON/XML 등 표현으로 자원을 다룸
- 자기 서술적 메시지: 요청/응답이 처리 방법을 포함
- HATEOAS: 응답에 관련 링크 포함 (실무에서는 잘 안 씀)
6. 온디맨드 코드 (Code on Demand) — 선택적
서버가 클라이언트에 실행 가능한 코드(JavaScript 등)를 전송할 수 있습니다. 선택적인 원칙입니다.
URI 설계 원칙
URI는 자원(명사) 을 나타내야 하며, 행위는 HTTP 메서드로 표현합니다.
-- 잘못된 URI 설계 --
GET /getUsers
POST /createUser
PUT /updateUser/1
DELETE /deleteUser/1
-- 올바른 URI 설계 --
GET /users -- 사용자 목록 조회
POST /users -- 사용자 생성
GET /users/{id} -- 특정 사용자 조회
PUT /users/{id} -- 사용자 전체 수정
PATCH /users/{id} -- 사용자 부분 수정
DELETE /users/{id} -- 사용자 삭제
URI 설계 규칙
-- 명사 사용 (동사 X) --
/articles O
/getArticles X
-- 소문자 사용 --
/user-profiles O
/userProfiles X
-- 하이픈(-) 사용, 언더스코어(_) 지양 --
/blog-posts O
/blog_posts X
-- 계층 관계 표현 --
/users/{userId}/posts -- 특정 사용자의 게시글
/users/{userId}/posts/{postId} -- 특정 사용자의 특정 게시글
/users/{userId}/posts/{postId}/comments -- 댓글
-- 컬렉션은 복수형 --
/users O
/user X
HTTP 메서드 활용
// Express.js 예시
const express = require('express');
const router = express.Router();
// GET — 조회 (Safe, Idempotent)
router.get('/users', async (req, res) => {
const { page = 1, limit = 20, search } = req.query;
const users = await UserService.findAll({ page, limit, search });
res.json({
data: users,
pagination: { page, limit, total: users.total },
});
});
// POST — 생성 (Not Safe, Not Idempotent)
router.post('/users', async (req, res) => {
const user = await UserService.create(req.body);
res.status(201).json(user); // 201 Created
});
// GET — 단건 조회
router.get('/users/:id', async (req, res) => {
const user = await UserService.findById(req.params.id);
if (!user) return res.status(404).json({ message: 'User not found' });
res.json(user);
});
// PUT — 전체 수정 (Idempotent)
router.put('/users/:id', async (req, res) => {
// body의 모든 필드로 완전 교체
const user = await UserService.replace(req.params.id, req.body);
res.json(user);
});
// PATCH — 부분 수정
router.patch('/users/:id', async (req, res) => {
// body의 필드만 업데이트
const user = await UserService.update(req.params.id, req.body);
res.json(user);
});
// DELETE — 삭제 (Idempotent)
router.delete('/users/:id', async (req, res) => {
await UserService.delete(req.params.id);
res.status(204).send(); // 204 No Content
});
HTTP 상태 코드 활용
올바른 상태 코드 사용은 RESTful API의 중요한 부분입니다.
// 성공 코드
// 200 OK — 일반적인 성공
// 201 Created — 리소스 생성 성공
// 204 No Content — 성공했으나 반환할 데이터 없음 (DELETE 등)
// 클라이언트 오류
// 400 Bad Request — 요청 형식 오류
// 401 Unauthorized — 인증 필요
// 403 Forbidden — 인증은 됐지만 권한 없음
// 404 Not Found — 리소스 없음
// 409 Conflict — 리소스 충돌 (중복 이메일 등)
// 422 Unprocessable Entity — 유효성 검사 실패
// 서버 오류
// 500 Internal Server Error — 서버 내부 오류
// 503 Service Unavailable — 서비스 불가
// 에러 응답 예시
function sendError(res, status, code, message) {
res.status(status).json({
error: {
code,
message,
timestamp: new Date().toISOString(),
},
});
}
// 사용 예
sendError(res, 404, 'USER_NOT_FOUND', '해당 사용자를 찾을 수 없습니다.');
sendError(res, 400, 'INVALID_EMAIL', '유효하지 않은 이메일 형식입니다.');
API 버전 관리
API가 발전함에 따라 하위 호환성을 유지하기 위한 버전 관리가 필요합니다.
-- URI 버전 관리 (가장 일반적) --
/api/v1/users
/api/v2/users
-- 헤더 버전 관리 --
GET /api/users HTTP/1.1
Accept: application/vnd.myapp.v2+json
-- 쿼리 파라미터 버전 관리 --
GET /api/users?version=2
RESTful API 예시 전체 구조
블로그 API 예시:
POST /api/v1/auth/login -- 로그인
POST /api/v1/auth/logout -- 로그아웃
POST /api/v1/auth/refresh -- 토큰 갱신
GET /api/v1/posts -- 게시글 목록 (페이지네이션)
POST /api/v1/posts -- 게시글 작성
GET /api/v1/posts/{id} -- 게시글 조회
PATCH /api/v1/posts/{id} -- 게시글 수정
DELETE /api/v1/posts/{id} -- 게시글 삭제
GET /api/v1/posts/{id}/comments -- 댓글 목록
POST /api/v1/posts/{id}/comments -- 댓글 작성
DELETE /api/v1/posts/{id}/comments/{commentId} -- 댓글 삭제
POST /api/v1/posts/{id}/likes -- 좋아요
DELETE /api/v1/posts/{id}/likes -- 좋아요 취소
정리
| 원칙 | 핵심 내용 |
|---|---|
| 클라이언트-서버 분리 | UI와 데이터 로직의 독립적 발전 |
| 무상태성 | 서버에 세션 저장 금지, 매 요청이 독립적 |
| 캐시 가능 | 응답에 캐시 여부 명시 |
| 계층화 시스템 | 중간 서버의 존재를 클라이언트가 몰라도 됨 |
| 균일한 인터페이스 | URI로 자원, HTTP 메서드로 행위 |
| 온디맨드 코드 | 선택적, 실행 가능 코드 전송 가능 |
RESTful API는 직관적이고 표준화된 인터페이스를 제공하여 다양한 클라이언트(웹, 모바일, IoT)와 쉽게 연동할 수 있습니다.