REST API 설계 시 꼭 지켜야 할 구조와 원칙
현대 웹 개발에서 REST API는 클라이언트와 서버 간 통신의 표준으로 자리 잡았습니다. 하지만 단순히 HTTP 메서드와 JSON을 사용한다고 해서 RESTful한 것은 아닙니다. 일관된 구조, 명확한 자원 표현, 적절한 상태 코드 등 체계적인 설계 원칙을 따를 때 비로소 유지보수가 쉽고 확장성 높은 API가 완성됩니다. 이번 글에서는 REST API 설계의 핵심 원칙부터 실전 구조, 자주 하는 실수까지 상세히 정리해 드리겠습니다.
REST API란?
REST(Representational State Transfer)는 로이 필딩(Roy Fielding)이 2000년 박사학위 논문에서 제안한 아키텍처 스타일입니다. REST API는 이 원칙을 기반으로, HTTP 프로토콜을 활용하여 자원(Resource)을 URI로 식별하고, HTTP 메서드로 자원에 대한 행위를 표현하는 방식입니다.
REST의 핵심 특징은 무상태(Stateless)입니다. 서버는 클라이언트의 이전 요청을 기억하지 않으며, 각 요청은 독립적으로 처리됩니다. 이 덕분에 서버 확장(Scale-out)이 용이하고, 요청 간 의존성이 제거됩니다.
URI 설계 원칙
URI(Uniform Resource Identifier)는 API의 얼굴입니다. 직관적이고 일관된 URI 설계는 API 사용성을 크게 좌우합니다.
📋 명사를 사용하고 동사는 피하라
URI는 자원을 표현하는 명사로 구성해야 합니다. 행위는 HTTP 메서드(GET, POST, PUT, DELETE)로 표현하므로 URI에 동사를 포함하지 마세요.
❌ GET /getUsers
❌ POST /createOrder
❌ DELETE /deleteProduct/123
✅ GET /users
✅ POST /orders
✅ DELETE /products/123
📋 복수형을 사용하라
컬렉션(Collection)을 표현할 때는 복수형 명사를 사용하는 것이 일반적입니다. 단수와 복수를 혼용하지 말고, 프로젝트 전체에서 통일하세요.
❌ GET /user/1
❌ GET /users/1/profile
✅ GET /users/1
✅ GET /users/1/profiles
📋 계층 구조로 자원 관계 표현
자원 간의 관계는 슬래시(/)로 계층을 구분하여 표현합니다. 너무 깊은 중첩은 피하고, 3단계 이상이 필요하다면 쿼리 파라미터를 고려하세요.
✅ GET /users/1/orders # 사용자 1의 주문 목록
✅ GET /users/1/orders/5 # 사용자 1의 5번 주문
⚠️ GET /users/1/orders/5/items/3 # 너무 깊음, /items?order_id=5 권장
📋 소문자와 하이픈 사용
URI는 소문자로 작성하고, 단어 구분이 필요할 때는 하이픈(-)을 사용합니다. 언더스코어(_)나 카멜케이스는 피하세요.
❌ GET /UserProfiles
❌ GET /user_profiles
❌ GET /userProfiles
✅ GET /user-profiles
HTTP 메서드와 행위 매핑
HTTP 메서드는 자원에 대한 CRUD 행위를 표현합니다. 메서드를 적절히 사용하면 API의 의도가 명확해집니다.
| 메서드 | 행위 | 멱등성 | 사용 예시 |
|---|---|---|---|
| GET | 자원 조회 | ✅ | GET /users/1 |
| POST | 자원 생성 | ❌ | POST /users |
| PUT | 자원 전체 교체 | ✅ | PUT /users/1 |
| PATCH | 자원 부분 수정 | ❌ | PATCH /users/1 |
| DELETE | 자원 삭제 | ✅ | DELETE /users/1 |
🔍 POST vs PUT vs PATCH 차이
POST /users
# 새로운 사용자 생성, 서버가 ID 할당
# 요청 본문: { "name": "홍길동", "email": "hong@example.com" }
PUT /users/1
# 사용자 1의 정보를 요청 본문으로 완전히 교체
# 누락된 필드는 null 또는 기본값으로 초기화됨
# 요청 본문: { "name": "김철수", "email": "kim@example.com" }
PATCH /users/1
# 사용자 1의 특정 필드만 부분 수정
# 요청 본문: { "name": "김철수" } # email은 변경 없음
응답 구조와 상태 코드
일관된 응답 형식과 적절한 HTTP 상태 코드는 API의 신뢰성을 높입니다.
📋 표준 응답 본문 구조
성공과 실패 모두 일관된 JSON 구조를 유지하면 클라이언트 처리가 단순해집니다.
// 성공 응답 (200 OK)
{
"success": true,
"data": {
"id": 1,
"name": "홍길동",
"email": "hong@example.com"
},
"message": null
}
// 성공 응답 — 컬렉션 (200 OK)
{
"success": true,
"data": [
{ "id": 1, "name": "홍길동" },
{ "id": 2, "name": "김철수" }
],
"pagination": {
"page": 1,
"size": 20,
"total": 150
}
}
// 실패 응답 (400 Bad Request)
{
"success": false,
"data": null,
"error": {
"code": "INVALID_EMAIL",
"message": "이메일 형식이 올바르지 않습니다.",
"field": "email"
}
}
📋 주요 HTTP 상태 코드
| 상태 코드 | 의미 | 사용 상황 |
|---|---|---|
| 200 OK | 성공 | GET, PUT, PATCH 성공 |
| 201 Created | 생성 완료 | POST로 자원 생성 성공 |
| 204 No Content | 성공, 본문 없음 | DELETE 성공 |
| 400 Bad Request | 잘못된 요청 | 요청 본문 형식 오류, 유효성 검사 실패 |
| 401 Unauthorized | 인증 필요 | 로그인 필요, 토큰 누락 |
| 403 Forbidden | 권한 없음 | 인증은 되었으나 접근 권한 없음 |
| 404 Not Found | 자원 없음 | 존재하지 않는 URI 또는 자원 |
| 409 Conflict | 충돌 | 중복 데이터, 동시성 충돌 |
| 422 Unprocessable | 처리 불가 | 형식은 맞지만 비즈니스 규칙 위반 |
| 500 Internal Error | 서버 오류 | 예상치 못한 서버 내부 오류 |
페이지네이션과 필터링
대량 데이터를 조회할 때는 페이지네이션과 필터링을 체계적으로 설계해야 합니다.
📋 오프셋 기반 페이지네이션
GET /users?page=1&size=20
GET /users?page=3&size=20&sort=created_at,desc
// 응답
{
"success": true,
"data": [ ... ],
"pagination": {
"page": 3,
"size": 20,
"total": 150,
"total_pages": 8
}
}
📋 커서 기반 페이지네이션
대용량 데이터에서 오프셋 방식은 성능이 저하됩니다. 이때는 커서 기반 방식을 사용하세요.
GET /users?cursor=eyJpZCI6MTAwfQ==&limit=20
// 응답
{
"success": true,
"data": [ ... ],
"pagination": {
"next_cursor": "eyJpZCI6MTIwfQ==",
"has_next": true
}
}
📋 필터링과 검색
GET /users?status=active&role=admin
GET /products?category=electronics&price_min=10000&price_max=50000
GET /orders?search=홍길동&start_date=2024-01-01&end_date=2024-12-31
필터는 쿼리 파라미터로, 자원 식별은 경로 변수로 구분하세요.
버전 관리와 보안
API는 시간이 지나면서 진화합니다. 하위 호환성을 유지하면서 안전하게 업데이트하려면 체계적인 버전 관리가 필요합니다.
📋 API 버전 관리 방식
// URL 경로에 버전 포함 (가장 일반적)
GET /v1/users
GET /v2/users
// 헤더에 버전 포함
GET /users
Accept: application/vnd.api.v1+json
// 쿼리 파라미터로 버전 지정
GET /users?api-version=1.0
URL 경로 방식이 가장 직관적이고 디버깅이 쉬워 실무에서 가장 많이 사용됩니다.
📋 인증과 인가
// Authorization 헤더에 Bearer 토큰 사용
GET /users/1
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
// API Key 방식 (내부 서비스 간 통신)
GET /internal/reports
X-API-Key: sk_live_1234567890abcdef
민감한 정보는 헤더에 포함하고, URL이나 쿼리 파라미터에는 노출하지 마세요.
자주 하는 실수와 해결책
REST API 설계에서 반복적으로 발생하는 실수들을 정리합니다.
❌ URI에 동사 사용
❌ GET /getUserById/1
❌ POST /createNewOrder
✅ GET /users/1
✅ POST /orders
❌ 200 OK에 에러 본문 반환
❌ HTTP/1.1 200 OK
{
"error": "사용자를 찾을 수 없습니다."
}
✅ HTTP/1.1 404 Not Found
{
"success": false,
"error": {
"code": "USER_NOT_FOUND",
"message": "사용자를 찾을 수 없습니다."
}
}
❌ 복수형과 단수형 혼용
❌ GET /user/1
❌ GET /users/1/order
✅ GET /users/1
✅ GET /users/1/orders
❌ 깊은 중첩 URI
❌ GET /users/1/orders/5/items/3/reviews/10
✅ GET /reviews/10
✅ GET /reviews?user_id=1&order_id=5&item_id=3
실무 체크리스트
REST API를 설계하고 검증할 때 다음 항목들을 확인하세요.
- ✅ URI는 명사 복수형으로 작성하고 동사는 피할 것
- ✅ HTTP 메서드로 CRUD 행위를 표현할 것
- ✅ 적절한 상태 코드를 반환할 것 (200, 201, 400, 404, 500 등)
- ✅ 일관된 응답 본문 구조를 유지할 것
- ✅ 페이지네이션을 기본으로 설계할 것
- ✅ API 버전 관리를 처음부터 적용할 것
- ✅ 인증 정보는 헤더에 포함할 것
- ✅ 에러 응답에 코드와 메시지를 모두 포함할 것
- ✅ HATEOAS를 고려하여 관련 리소스 링크를 제공할 것 (선택)
- ✅ OpenAPI(Swagger)로 API 문서를 자동화할 것
자주 묻는 질문
REST API와 GraphQL 중 어떤 것을 선택해야 하나요?
프로젝트의 특성에 따라 다릅니다. REST API는 단순하고 직관적이며 캐싱이 용이해 일반적인 CRUD 중심 서비스에 적합합니다. GraphQL은 클라이언트가 필요한 데이터만 선택적으로 가져올 수 있어 복잡한 조회가 많은 경우 유리합니다. 대부분의 경우 REST로 시작하고, 필요 시 GraphQL을 도입하는 것이 현실적입니다.
PUT과 PATCH 중 어떤 것을 사용해야 하나요?
전체 자원을 교체할 때는 PUT, 일부 필드만 수정할 때는 PATCH를 사용합니다. PUT은 멱등성이 보장되므로 안전하게 재시도할 수 있습니다. PATCH는 부분 수정에 편리하지만 멱등성이 항상 보장되지는 않습니다. API 설계 시 클라이언트의 사용 패턴을 고려하여 결정하세요.
에러 응답에 어떤 정보를 포함해야 하나요?
최소한 에러 코드(machine-readable)와 에러 메시지(human-readable)를 포함해야 합니다. 가능하다면 어떤 필드에서 문제가 발생했는지도 함께 알려주면 클라이언트에서 폼 유효성 검사와 연동하기 쉽습니다.
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "입력값을 확인해주세요.",
"details": [
{ "field": "email", "message": "이메일 형식이 올바르지 않습니다." },
{ "field": "password", "message": "비밀번호는 8자 이상이어야 합니다." }
]
}
}
마무리
이번 글에서는 REST API 설계 시 꼭 지켜야 할 구조와 원칙을 자세히 살펴보았습니다. REST API는 단순히 HTTP 메서드와 JSON을 사용하는 것 이상의 체계적인 설계 철학이 필요합니다. 일관된 URI, 적절한 상태 코드, 명확한 응답 구조는 API의 사용성과 유지보수성을 결정합니다.
핵심을 정리하면 다음과 같습니다: URI는 명사 복수형으로 작성하고 HTTP 메서드로 행위를 표현하며, 상태 코드를 정확히 사용하고 일관된 응답 구조를 유지하며, 페이지네이션과 버전 관리를 처음부터 설계하세요.
REST API 설계 관련 궁금한 점이나 문제가 있으시면 댓글로 남겨 주세요. 도움이 되셨다면 이 글을 주변에 공유해 주시는 것도 잊지 마세요!


댓글 0
첫 댓글을 남겨보세요.