DEV Community

Cover image for REST API 명명 규칙: 실용 스타일 가이드
Rihpig
Rihpig

Posted on Originally published at apidog.com

REST API 명명 규칙: 실용 스타일 가이드

2년 이상 된 코드베이스를 열어보면 /getUser, /user_list, [REDACTED PATH]할 수 있습니다. 당장 장애를 일으키지는 않지만, 이런 불일치는 팀 전체를 느리게 만듭니다. 이름 지정은 가장 저렴한 API 설계 결정이지만, 되돌리기에는 가장 비쌉니다. 클라이언트가/getOrders`에 의존하기 시작하면 수년간 이를 지원해야 합니다. 이 가이드는 REST API 이름 지정에 적용할 구체적인 규칙과 예시, 반례를 정리합니다. 더 광범위한 개발자를 위한 REST API 가이드라인과 같은 원칙을 따르되, 팀에서 가장 많이 논쟁하는 “무엇을 어떻게 부를 것인가”에 집중합니다. 코드 리뷰 댓글 대신 도구로 규칙을 적용하려면 Apidog에서 코드를 작성하기 전에 모든 엔드포인트를 공유 스키마로 시각적으로 정의할 수 있습니다.

{% cta https://apidog.com/?utm_source=dev.to&utm_medium=wanda&utm_content=n8n-post-automation %} 지금 Apidog를 사용해 보세요 {% endcta %}

컬렉션에는 복수 명사를 사용하세요

URL은 작업이 아니라 리소스를 식별해야 합니다. 여러 리소스의 집합인 컬렉션은 복수 명사로 표현하세요.

사용 권장:

http
GET /v1/products
GET /v1/products/89
GET /v1/orders

사용 지양:

http
GET /v1/getProducts
GET /v1/product
GET /v1/productList

복수형은 컬렉션과 개별 항목 모두에 자연스럽습니다.

  • /products: 제품 컬렉션
  • /products/89: 해당 컬렉션의 89번 제품

단수형을 사용하면 하나의 항목에는 /product/89, 여러 항목에는 /product를 사용하는 어색한 구조가 생깁니다. Microsoft REST API 가이드라인과 Stripe, GitHub, Shopify 같은 주요 공개 API도 이 이유로 복수 명사를 사용합니다.

단, 진정한 싱글톤 리소스는 예외입니다. 사용자에게 장바구니가 하나뿐이라면 /users/42/cart처럼 단수형을 사용해도 됩니다.

경로에서 동사를 제외하세요

HTTP 메서드가 이미 동작을 표현합니다. 경로에는 리소스 이름만 두세요.

사용 권장:

http
GET /v1/orders/42
DELETE /v1/orders/42
PATCH /v1/orders/42

사용 지양:

http
GET /v1/fetchOrder/42
POST /v1/deleteOrder/42
POST /v1/updateOrderStatus

동사 기반 경로는 문서화, 테스트, 캐싱해야 할 엔드포인트 수를 불필요하게 늘립니다. 동일한 URL에 HTTP 메서드만 다르게 적용하면 캐시 무효화도 단순해집니다.

예를 들어 CDN은 다음처럼 같은 리소스를 기준으로 캐시와 무효화를 처리할 수 있습니다.

http
GET /v1/orders/42
DELETE /v1/orders/42

반면 /fetchOrder/42/deleteOrder/42는 서로 다른 URL이므로 연결하기 어렵습니다.

URL 경로에는 케밥 케이스(kebab-case)를 사용하세요

여러 단어로 구성된 경로 세그먼트는 하이픈으로 구분하세요.

사용 권장:

text
/v1/gift-cards
/v1/shipping-addresses

사용 지양:

text
/v1/giftCards
/v1/gift_cards
/v1/GiftCards

하이픈을 권장하는 이유는 세 가지입니다.

  1. Google이 하이픈을 단어 구분자로 처리합니다.
  2. 밑줄은 URL에 밑줄이 그어질 때 잘 보이지 않을 수 있습니다.
  3. 카멜 케이스는 대소문자 구분 오류를 유발합니다. /giftCards/giftcards는 대부분의 서버에서 서로 다른 URL입니다.

Zalando RESTful API 가이드라인도 케밥 케이스를 MUST 규칙으로 정의합니다.

JSON 표기법 하나를 선택하고 문서화하세요

요청과 응답 본문의 필드 이름에는 카멜 케이스와 스네이크 케이스 모두 사용할 수 있습니다. 문제는 두 표기법을 섞는 것입니다.

사용 권장: 둘 중 하나를 모든 곳에서 일관되게 사용

json
{
"orderId": 42,
"createdAt": "2026-08-30T09:15:00Z",
"totalAmount": 4999
}

json
{
"order_id": 42,
"created_at": "2026-08-30T09:15:00Z",
"total_amount": 4999
}

사용 지양:

json
{
"orderId": 42,
"created_at": "2026-08-30T09:15:00Z",
"TotalAmount": 4999
}

카멜 케이스는 JavaScript와 Java 클라이언트에 잘 매핑됩니다. 스네이크 케이스는 읽기 쉽고 Ruby, Python, SQL 열 이름과 잘 맞습니다. Stripe는 전체 API에서 스네이크 케이스를 사용합니다.

가장 많은 소비자가 사용하는 언어와 기존 생태계를 기준으로 하나를 선택한 뒤 스타일 가이드에 기록하세요. 혼합 표기법은 취향의 문제가 아니라 거버넌스의 실패입니다.

중첩은 두 단계로 제한하세요

중첩은 소유권을 표현할 때 유용합니다.

http
GET /v1[REDACTED PATH]
GET /v1/orders/1337/refunds

그러나 세 단계 이상 깊어지면 클라이언트가 모든 조상 ID를 전달해야 하므로 유지보수가 어려워집니다.

http
GET /v1[REDACTED PATH]

환불 ID가 전역적으로 고유하다면 다음 중 하나로 충분합니다.

http
GET /v1/refunds/7
GET /v1/orders/1337/refunds/7

좋은 냄새 테스트는 간단합니다. URL에 ID가 세 개 이상 포함되어 있다면 평면화를 검토하세요. 주문이 독립적인 리소스라면 /orders/1337만으로 접근할 수 있어야 합니다.

필터링, 정렬, 페이지네이션은 쿼리 파라미터로 처리하세요

경로는 리소스를 식별하고, 쿼리 파라미터는 리소스를 조회하는 방식을 수정합니다.

사용 권장:

http
GET /v1/orders?status=active&sort=-created_at&limit=50&cursor=eyJpZCI6NDJ9
GET /v1/products?category=electronics&min_price=1000

사용 지양:

http
GET /v1/orders/active
GET /v1/orders/sorted-by-date-desc
GET /v1/getOrdersByStatusAndDate

sort=-created_at처럼 마이너스 접두사로 내림차순을 표현하는 방식은 JSON:API 사양에서 비롯되었습니다. 별도의 order=desc 파라미터도 필요하지 않습니다.

/orders/active는 처음에는 간단해 보이지만, 여러 필터를 조합하는 순간 조합별 엔드포인트가 필요해집니다. 필터는 쿼리 파라미터로 유지하세요.

페이지네이션 이름도 하나를 선택해 모든 컬렉션에서 재사용해야 합니다.

text
limit + cursor

또는:

text
page + per_page

커서와 오프셋 방식의 트레이드오프는 API 페이지네이션 가이드에서 자세히 확인할 수 있습니다. 여기서 중요한 것은 방식보다 일관성입니다.

경로에 주 버전을 포함하세요

대표적인 API 버전 관리 방식은 다음 두 가지입니다.

http
/v1/products

http
Accept: application/vnd.myapi.v1+json

헤더 버전 관리는 URL을 버전 간에 동일하게 유지한다는 점에서 더 “순수한” REST로 볼 수 있습니다. Google API 설계 지침도 두 방식 모두 널리 사용된다고 설명합니다.

하지만 운영 측면에서는 경로 버전 관리가 실용적입니다.

  • 로그에서 버전이 바로 보입니다.
  • 브라우저와 curl로 테스트하기 쉽습니다.
  • Vary 헤더 없이 캐시할 수 있습니다.
  • 클라이언트가 버전 헤더를 누락할 가능성이 없습니다.

따라서 다음처럼 주 버전만 경로에 포함하는 방식을 권장합니다.

http
/v1/products

다음과 같은 세부 버전은 피하세요.

http
/v1.2/products

사소한 변경은 하위 호환성을 유지해야 합니다. 콘텐츠 협상을 포함한 전체 판단 기준은 API 버전 관리 전략 비교를 참고하세요.

리소스 ID는 불투명하게 처리하세요

다음처럼 순차적인 정수 ID를 공개하면 처리량이 노출되고 ID 열거 공격에 취약해집니다.

http
/orders/41
/orders/42
/orders/43

공격자는 ID 공간을 순회하며 권한 검사가 누락된 객체를 찾을 수 있습니다. 이러한 깨진 객체 수준 권한 부여(BOLA)는 OWASP API Security Top 10에서 1위를 차지합니다.

사용 권장:

http
GET /v1/orders/ord_9f8e2a71b3
GET /v1[REDACTED PATH]e8400-e29b-41d4-a716-446655440000

사용 지양:

http
GET /v1/orders/42
GET /v1/invoices/10883

Stripe의 ord_9f8e2a71b3처럼 접두사가 있는 무작위 ID는 추측하기 어렵고 로그에서도 의미를 파악하기 쉽습니다.

단, 불투명한 ID가 권한 부여 검사를 대체하지는 않습니다. 이는 누락된 검사의 폭발 반경을 줄일 뿐입니다. 내부 데이터베이스에서는 정수 기본 키를 사용해도 되며, 이 규칙은 외부 URL에 어떤 ID를 노출할지에 관한 것입니다.

비-CRUD 작업은 컨트롤러 리소스로 모델링하세요

주문 취소, 결제 재시도, 이메일 재전송처럼 CRUD로 표현하기 어려운 작업이 있습니다. 이런 작업을 상태 필드 업데이트로 숨기거나 최상위 경로에 동사를 추가하지 마세요.

사용 권장:

http
POST /v1/orders/42/cancel
POST /v1/payments/pay_88a1/retry

사용 지양:

`http
PATCH /v1/orders/42
{ "status": "cancelled" }

POST /v1/cancelOrder
{ "orderId": 42 }
`

이것이 컨트롤러 패턴입니다. 동사는 유일하게 경로 마지막에, 작업 대상 리소스의 범위 안에서 사용할 수 있습니다.

주문 취소는 환불, 재고 해제, 알림 전송을 발생시킬 수 있습니다. 이를 단순한 필드 쓰기처럼 처리하면 서버가 페이로드를 비교해 의도를 추론해야 합니다. /cancel 엔드포인트를 사용하면 다음을 명확히 분리할 수 있습니다.

  • 작업의 의도
  • 작업별 권한
  • 감사 추적
  • 취소 사유 같은 작업별 입력

헤더와 쿼리 파라미터 표기법도 일관되게 유지하세요

사용자 정의 헤더는 HTTP 관례에 맞춰 하이픈-파스칼-케이스를 사용하세요.

http
Idempotency-Key
Request-Id

오래된 X- 접두사는 사용하지 않는 것이 좋습니다. RFC 6648에 따라 폐기되었기 때문입니다. 헤더 이름은 전송 시 대소문자를 구분하지 않지만, 문서와 SDK에서는 한 가지 표기를 유지해야 합니다.

쿼리 파라미터는 JSON 본문 표기법과 맞추세요. 본문이 스네이크 케이스라면 다음처럼 작성합니다.

http
?min_price=1000&created_after=2026-01-01

다음처럼 혼용하지 마세요.

http
?minPrice=1000

응답에서는 created_at을 읽고 요청에서는 createdAfter를 입력하게 만들면 실수가 반복됩니다.

전체 규칙 요약

# 규칙 권장 지양
1 컬렉션에는 복수 명사 사용 /products, /products/89 /getProducts, /productList
2 경로에 동사 없음 DELETE /orders/42 POST /deleteOrder/42
3 케밥 케이스 경로 세그먼트 /gift-cards /giftCards, /gift_cards
4 JSON 표기법 하나를 선택하고 문서화 모든 곳에서 order_id orderIdorder_id 혼용
5 중첩은 최대 두 단계 /orders/1337/refunds `[REDACTED PATH]
6 필터·정렬·페이지네이션은 쿼리 사용 ?status=active&sort=-created_at /orders/active
7 경로에 주 버전 포함 /v1/products /v1.2/products, 버전 헤더
8 불투명한 리소스 ID 사용 /orders/ord_9f8e2a71b3 공개된 /orders/42
9 작업에는 컨트롤러 패턴 사용 POST /orders/42/cancel PATCHstatus 변경
10 헤더·파라미터 표기법 통일 Idempotency-Key, ?min_price= X-IDEMPOTENCY_KEY, ?minPrice= 혼용

규모에 맞춰 컨벤션을 적용하세요

위키에 스타일 가이드를 작성하는 것만으로는 충분하지 않습니다. 일관된 API를 만드는 팀은 코드가 존재하기 전에 설계하고, 그 단계에서 컨벤션을 적용합니다. 이것이 API 거버넌스의 핵심입니다.

Apidog에서는 스키마 우선 시각 디자이너로 엔드포인트를 정의할 수 있습니다. 경로, 표기법, 파라미터 이름이 컨트롤러 코드에 묻히지 않고 명시적인 설계 아티팩트로 남습니다.

공유 컴포넌트를 사용하면 다음 스키마를 한 번 정의하고 모든 엔드포인트에서 재사용할 수 있습니다.

  • Pagination
  • Error
  • Money

그 결과 새로운 서비스에서 per_pagepageSize로 다시 발명하는 일을 막을 수 있습니다. 팀 작업 공간에서 설계를 검토하면 /getUserOrders 같은 문제도 코드가 작성되기 전에 발견할 수 있습니다. 설계 단계에서는 이름 변경 비용이 작지만, 여러 클라이언트가 통합한 뒤에는 훨씬 커집니다.

스펙은 문서, 목업 서버, 테스트를 함께 구동하므로 승인된 이름이 실제 배포물에도 그대로 반영됩니다. Apidog를 다운로드해 다음 엔드포인트부터 무료로 사용해 보세요.

기존 API를 개선하는 일은 어렵지만, 새 API에 좋은 기준을 적용하는 일은 어렵지 않습니다.

FAQ

REST URL은 복수여야 할까요, 단수여야 할까요?

인스턴스가 여러 개일 수 있는 리소스에는 복수형을 사용하세요.

/products
/orders
/users
Enter fullscreen mode Exit fullscreen mode

복수형은 컬렉션(/orders)과 개별 멤버(/orders/42) 모두에 자연스럽습니다. `[REDACTED PATH] 모델링의 기본 원리는 REST API란 무엇인가에서 더 자세히 확인할 수 있습니다.

JSON 필드에는 카멜 케이스와 스네이크 케이스 중 무엇이 더 좋을까요?

어느 쪽도 절대적으로 우월하지 않습니다.

  • 카멜 케이스: JavaScript 중심 소비자에게 적합
  • 스네이크 케이스: 가독성이 좋고 Python, Ruby, Stripe와 잘 맞음

하나를 선택하고 스타일 가이드에 기록한 뒤 스키마 검토에서 적용하세요. 여러 엔드포인트에서 표기법을 섞는 것이 가장 큰 문제입니다.

API 버전을 URL에 넣어야 할까요, 헤더에 넣어야 할까요?

강력한 하이퍼미디어 요구 사항이 없다면 경로 버전을 사용하세요.

http
/v1/orders

경로 버전은 로그, 캐시, 브라우저 테스트에 자연스럽게 나타납니다. 헤더 버전은 URL을 안정적으로 유지하지만, 클라이언트가 헤더를 누락하면 조용히 잘못된 버전이 호출될 수 있습니다.

주 버전만 사용하고, 사소한 변경은 하위 호환성을 유지하는 방식으로 제공하세요.

REST API 경로에 동사를 사용해도 되나요?

비-CRUD 작업에 대한 컨트롤러 엔드포인트에서는 허용됩니다.

http
POST /orders/42/cancel
POST /payments/pay_88a1/retry

동사는 작업 대상 리소스 아래, 경로 마지막에 두고 메서드는 POST를 사용하세요. 그 외의 경우에는 HTTP 메서드가 동작을 표현하고, 경로는 명사로 유지해야 합니다.

Top comments (0)