"URI 는 API 계약의 가장 작고 가장 보이는 조각. 로그, dashboard, docs, 청구 보고서, 고객 에러 보고에 나타나. 제대로 해; 후회 안 함. 잘못 해; 몇 년 같이 살아."
결정 95% 커버하는 여섯 규칙
1. 명사, 동사 아님. URI 가 action 아닌 thing 이름. /users/42 지 /getUser/42 아님. Verb 는 HTTP method. GET /users/42 가 이미 "user 42 줘" 라고 함; GET /getUser/42 가 두 번 말하고 protocol verb 헷갈리게 함.
2. Collection 은 복수, item 은 단수. Collection 은 thing set: /users. Item 은 그 set 안 특정 thing: /users/{id}. Collection 에 복수 고수해 — /user/42 가 이상하게 읽히고 일관성 깸.
3. Nesting 은 ownership 반영, relationship 아님. /users/{id}/orders 는 "이 user 에 속한 order" 의미. 관계가 many-to-many 거나 ownership 의미 안 하면 top-level resource 와 query param 써: /orders?user_id={id} 가 /users/{id}/orders 보다.
4. 소문자, 단어 사이 hyphen. /account-settings/{id} 지 /AccountSettings/{id} 나 /account_settings/{id} 아님. URL 이 대부분 context 에서 대소문자 구분; 소문자가 모호함 제거. Hyphen 이 underscore 이김 — underscore 가 hyperlink 아래 시각적으로 숨겨짐 (account_settings vs account-settings 보면).
5. 파일 확장자 없음. /users/42 지 /users/42.json 아님. Format 은 Content-Type 에 속하지 URI 아님. URI 의 .json 이 representation 을 resource identity 에 coupling, 정확히 content negotiation 이 피하려고 존재하는 거.
6. 2-3 레벨 이상 nest 마. /users/{u}/orders/{o}/items/{i} 가 edge. /users/{u}/orders/{o}/items/{i}/comments/{c}/replies/{r} 는 설계 멈추고 API 와 논쟁 시작 신호. 안정 ID 가질 때 깊은 resource 를 top-level 로 promote 해서 평탄화 (/replies/{r}).
ID 선택 — Opaque 가 sequential 이김
흔한 ID 전략 둘:
- Sequential integer (
/users/1,/users/2): 싸고, 정렬 가능, debug 가능. ID 증가 보는 누구든 성장률 누설. 열거 쉬움 (/users/1,/users/2, ...). - Opaque string (
/users/usr_8x3kPq, UUID, ULID, Stripe-스타일 prefixed ID): 열거 안 됨, 성장률 누설 없음, 로그에서 grep 쉬움. 약간 덜 debug 가능 (외우기 어려움).
사용자 대면이나 공개 거면 opaque 선호. Stripe 의 prefix convention (customer 에 cus_, payment 에 pay_) 이 금 — ID 혼동 불가능. 내부 전용 ID 는 integer 유지 가능.
Action URI — 규칙 1 의 유일한 예외
진짜로 resource 아닌 operation 있을 때 convention 은 POST /resources/{id}/actionName. POST /payments/42/refund, POST /jobs/abc/cancel. Action 이름이 URI 에 나타나 — 쓸 명사 없어서. 이게 Lesson 3.1 의 하이브리드 패턴 — API 의 resource-oriented 나머지와 일관.
Bad → Good 걸어쓰기
흔한 smell 다섯과 fix:
BAD: GET /getUserById?id=42 (path 에 verb, ID 에 query)
GOOD: GET /users/42
BAD: POST /createOrder (path 에 verb)
GOOD: POST /orders
BAD: DELETE /users/42/delete (verb 가 method 와 중복)
GOOD: DELETE /users/42
BAD: GET /User/42.json (대소문자 + 확장자)
GOOD: GET /users/42 (Accept: application/json)
BAD: PATCH /user_settings_for/42 (snake + underscore + 어색)
GOOD: PATCH /users/42/settings
cwkPippa 의 URI 선택
POST /api/council/{id}/finalize 와 POST /api/heartbeat/cron/{id}/run-now 같은 action endpoint, action 이지 resource 아니라서 의도적으로 verb-suffix 씀. 그 의도적 예외 괜찮음; 추가하는 bar 는 "이 operation 에 진짜 명사 없어?".