[네트워크] - REST API 설계
1. REST란
1-1 정의
REST(Representational State Transfer)는 자원을 URI로 구분하고, 자원의 상태를 표현(representation)으로 주고받는 방식을 정한 아키텍처 스타일입니다.
- 로이 필딩(Roy Fielding)이 2000년 박사 논문에서 웹의 아키텍처 원칙을 정리하며 정의했습니다.
- 프로토콜이나 표준이 아니라, 지켜야 할 제약 조건의 모음입니다(2절).
- 이름의 뜻은 "자원 상태의 표현을 전송한다"입니다. 서버와 클라이언트가 주고받는 것은 자원 자체가 아니라, 자원의 상태를 JSON 같은 형식으로 옮긴 표현입니다.
1-2 구성 요소
- 자원(Resource): 다룰 대상입니다. URI로 식별합니다.
/api/users/1은 1번 회원입니다. - 행위(Verb): 자원에 할 일입니다. HTTP 메서드(GET, POST, PUT, PATCH, DELETE)로 나타냅니다.
- 표현(Representation): 자원의 상태를 담아 주고받는 형식입니다. 보통 JSON을 쓰고, 요청의
Accept헤더와 응답의Content-Type헤더로 형식을 알립니다.
1-3 REST API와 RESTful API
- REST API: REST의 설계 원칙을 활용해 만든 API 입니다.
- RESTful: REST의 원칙을 잘 준수하도 설계된 API를 강조하는 표현입니다. URI가 자원만 나타내고, 메서드와 상태 코드를 의미에 맞게 쓰는 API를 "RESTful하다"고 합니다.
- 실무에서는 두 말을 거의 같은 뜻으로 씁니다. 다만 모든 요청을
POST /api하나로 받고 본문에 행위를 담는 API는 HTTP를 쓰더라도 RESTful하다고 하지 않습니다.
2. REST의 제약 조건
2-1 여섯 가지 제약 조건
필딩은 아래 조건을 지키는 시스템을 REST라고 정의했습니다.
- 클라이언트-서버(Client-Server): 화면을 담당하는 클라이언트와 데이터를 담당하는 서버를 분리합니다. 둘은 API로만 통신하므로 서로 독립적으로 개발하고 배포할 수 있습니다.
- 무상태(Stateless): 서버는 요청 사이에 클라이언트의 상태를 저장하지 않습니다. 요청마다 처리에 필요한 정보를 모두 담아 보냅니다(2-2).
- 캐시 가능(Cacheable): 응답에 캐시해도 되는지를 표시합니다. HTTP의
Cache-Control,ETag같은 헤더가 이 역할을 합니다. - 균일한 인터페이스(Uniform Interface): 모든 자원을 같은 방식으로 다룹니다. 네 가지 세부 조건이 있습니다.
- 자원은 URI로 식별합니다.
- 자원은 표현(JSON 등)을 주고받아 조작합니다.
- 메시지는 스스로를 설명합니다. 메서드, 상태 코드,
Content-Type만 보고 요청과 응답의 의미를 알 수 있어야 합니다. - 응답에 다음 행위로 가는 링크를 담습니다(HATEOAS, 10절).
- 계층화 시스템(Layered System): 클라이언트와 서버 사이에 로드 밸런서, 프록시, 게이트웨이 같은 중간 계층을 둘 수 있습니다. 클라이언트는 자신이 서버와 직접 통신하는지 중간 계층과 통신하는지 알 필요가 없습니다.
- 코드 온 디맨드(Code on Demand): 서버가 자바스크립트 같은 실행 코드를 내려보내 클라이언트 기능을 확장할 수 있습니다. 여섯 가지 가운데 유일한 선택 사항입니다.
2-2 무상태성
- 정의: 서버가 이전 요청의 맥락을 기억하지 않는 것입니다. 각 요청은 그 요청만으로 처리할 수 있도록 인증 정보 같은 필요한 정보를 모두 담아야 합니다.
- 장점: 어떤 서버도 이전 요청을 기억할 필요가 없으므로, 로드 밸런서가 요청을 아무 서버로나 보내도 처리됩니다. 서버를 늘리는 수평 확장이 쉽습니다.
- 단점: 요청마다 인증 정보 같은 데이터를 반복해서 보내므로 요청이 커지고, 서버는 매 요청을 다시 검증해야 합니다.
- 세션과 토큰: 세션 방식은 서버가 로그인 상태를 저장하므로, 서버를 여러 대로 늘리면 로그인을 처리한 서버가 아닌 다른 서버는 그 상태를 모릅니다. 세션을 Redis 같은 공용 저장소에 두면 공유할 수 있지만, 서버 쪽이 상태를 저장한다는 점은 같습니다. 토큰(JWT) 방식은 요청마다 토큰에 인증 정보를 담아 보내므로 어느 서버에서든 검증할 수 있고, 무상태 제약에 맞습니다.
3. URI 설계
3-1 URI와 URL
- URI(Uniform Resource Identifier): 자원을 식별하는 문자열입니다.
- URL(Uniform Resource Locator): URI 가운데 자원의 위치와 접근 방법(
https://, 호스트, 경로)까지 나타내는 것입니다. - 웹 API의 주소는 URL이면서 URI입니다. REST에서는 자원을 식별한다는 뜻을 살려 주로 URI라고 부릅니다.
3-2 자원은 명사로, 행위는 메서드로
URI에는 자원만 쓰고, 행위는 HTTP 메서드로 나타냅니다. 메서드가 이미 행위를 나타내므로 URI에 get, find, delete, create 같은 동사를 다시 쓰지 않습니다.
GET /api/getUser?id=1 → GET /api/users/1
POST /api/users/delete/1 → DELETE /api/users/1
POST /api/createOrder → POST /api/orders
GET /api/user-info/1 → GET /api/users/1마지막 줄의 info, data처럼 무엇을 가리키는지 드러나지 않는 추상적인 단어도 이름에 쓰지 않습니다.
한 자원에 대한 API는 다음 모양이 됩니다. 기본 URI는 /api/users와 /api/users/{id} 두 가지이고, 무엇을 할지는 메서드가 정합니다.
GET /api/users 회원 목록 조회
GET /api/users/{id} 회원 단건 조회
POST /api/users 회원 생성
PUT /api/users/{id} 회원 정보 전체 수정
PATCH /api/users/{id} 회원 정보 일부 수정
DELETE /api/users/{id} 회원 삭제
GET /api/users/{id}/orders 회원의 주문 목록 조회3-3 표기 규칙
표준으로 정해진 규칙은 아니고, 널리 쓰이는 관례입니다.
- 경로는 소문자로 씁니다. URI 표준에서 스킴과 호스트를 제외한 경로는 대소문자를 구분하므로,
/Users와/users는 서로 다른 자원으로 처리될 수 있습니다. - 단어는 하이픈(-)으로 잇습니다.
/order-items처럼 씁니다. 밑줄(_)은 링크 밑줄에 가려 읽기 어려워서 피합니다. - 컬렉션은 복수형 명사로 씁니다.
/users는 회원 컬렉션,/users/1은 그중 1번 회원입니다. - 계층 관계는 슬래시(/)로 나타냅니다.
/users/1/orders는 1번 회원의 주문 목록입니다. 경로 마지막에는 슬래시를 붙이지 않습니다. - 파일 확장자를 넣지 않습니다.
/users/1.json대신Accept: application/json헤더로 형식을 정합니다. - 경로와 JSON 필드의 규칙은 따로 정합니다. 경로는 소문자와 하이픈을 쓰고, JSON 본문의 필드 이름은 카멜 케이스(
createdAt)를 많이 씁니다. - 어떤 규칙을 고르든 팀에서 하나로 정해 문서에 적습니다.
3-4 경로 변수와 쿼리 파라미터
- 경로 변수(Path Variable): 특정 자원을 식별할 때 씁니다.
/users/{id}의id가 바뀌면 가리키는 자원이 바뀝니다. - 쿼리 파라미터(Query Parameter): URL의
?뒤 쿼리 스트링에키=값으로 붙이는 값입니다. 컬렉션을 필터링·정렬·페이징·검색할 때 씁니다./users?role=ADMIN&sort=createdAt,desc&page=0&size=20처럼 씁니다. - 판단 기준: 값이 "어떤 자원인가"를 정하면 경로 변수, "그 자원을 어떻게 보여 줄 것인가"를 정하면 쿼리 파라미터입니다.
- 민감 정보는 URL에 담지 않습니다. 경로와 쿼리 스트링은 브라우저 기록과 서버 로그에 그대로 남으므로, 비밀번호나 토큰은 요청 본문이나 헤더로 보냅니다.
3-5 CRUD로 표현하기 어려운 기능
로그인, 주문 취소, 구독처럼 생성·조회·수정·삭제에 바로 대응하지 않는 기능은 다음 순서로 설계합니다.
- 자원의 상태 변경으로 표현합니다. 주문 취소는 주문의 상태를 바꾸는 것이므로
PATCH /api/orders/{id}에{"status": "CANCELED"}를 보냅니다. - 행위 자체를 자원으로 만듭니다. 플레이리스트 구독은 구독이라는 자원을 생성하는 것으로 봅니다. 구독은
POST /api/playlists/{id}/subscriptions, 구독 취소는DELETE /api/playlists/{id}/subscriptions입니다. - 그래도 어렵다면 마지막 경로에만 동사를 예외로 둡니다.
POST /api/auth/login,POST /api/orders/{id}/cancel처럼 씁니다. 이런 예외는 팀 규칙으로 정해 문서에 적습니다.
4. HTTP 메서드
4-1 메서드별 의미
- GET: 자원을 조회합니다. 서버 상태를 바꾸지 않습니다.
- POST: 자원을 생성하거나, 다른 메서드로 표현하기 어려운 처리를 요청합니다. 컬렉션 URI(
/users)에 보내면 서버가 새 자원의 URI를 정합니다. - PUT: 대상 자원을 요청 본문의 내용으로 통째로 교체합니다. 대상 자원이 없으면 생성할 수도 있습니다.
- PATCH: 대상 자원의 일부만 수정합니다.
- DELETE: 자원을 삭제합니다.
- 이 밖에 본문 없이 헤더만 받는 HEAD, 서버가 지원하는 메서드를 확인하는 OPTIONS가 있습니다. 브라우저는 CORS 사전 요청(preflight)에 OPTIONS를 씁니다.
4-2 PUT과 PATCH
두 메서드 모두 수정에 쓰지만, 보내지 않은 필드를 어떻게 처리하느냐가 다릅니다.
현재 상태 { "nickname": "kim", "email": "kim@example.com", "bio": "백엔드 개발자" }
PUT /api/users/1 { "nickname": "lee", "email": "kim@example.com" }
결과 { "nickname": "lee", "email": "kim@example.com", "bio": null }
PATCH /api/users/1 { "nickname": "lee" }
결과 { "nickname": "lee", "email": "kim@example.com", "bio": "백엔드 개발자" }- PUT은 요청 본문이 자원의 새 전체 상태입니다. PUT의 의미대로라면 보내지 않은
bio는 비워집니다. 그래서 클라이언트는 바꾸지 않는 필드까지 모두 보내야 합니다. - PATCH는 요청 본문에 바꿀 필드만 담습니다. 보내지 않은 필드는 그대로 남습니다.
- 멱등성: PUT은 같은 요청을 여러 번 보내도 결과 상태가 같으므로 멱등합니다. PATCH는 표준상 멱등성이 보장되지 않습니다. "닉네임을 lee로 바꿔라"는 여러 번 보내도 결과가 같지만, "조회수를 1 올려라" 같은 요청은 보낼 때마다 결과가 달라지기 때문입니다.
5. 안전성과 멱등성
5-1 정의
- 안전한 메서드(Safe): 서버의 상태를 바꾸지 않는 메서드입니다. 읽기 전용입니다.
- 멱등한 메서드(Idempotent): 같은 요청을 한 번 보내든 여러 번 보내든 서버의 상태가 같은 메서드입니다.
- 멱등성은 서버 상태를 기준으로 판단하고, 응답은 달라도 됩니다.
DELETE /api/users/1을 두 번 보내면 첫 번째는 204를, 두 번째는 이미 없으므로 404를 받습니다. 그래도 "1번 회원이 없다"는 서버 상태는 같으므로 DELETE는 멱등합니다. - 안전한 메서드는 상태를 바꾸지 않으므로 모두 멱등합니다. 반대로 멱등하다고 안전한 것은 아닙니다. PUT과 DELETE는 상태를 바꾸지만 멱등합니다.
5-2 메서드별 정리
| 메서드 | 안전 | 멱등 |
|---|---|---|
| GET | O | O |
| POST | X | X |
| PUT | X | O |
| PATCH | X | X |
| DELETE | X | O |
- HEAD와 OPTIONS도 안전하고 멱등합니다.
- PATCH는 표준상 멱등하지 않지만, "특정 값으로 바꾼다"는 요청만 받도록 설계하면 멱등하게 만들 수 있습니다.
5-3 멱등성이 중요한 이유: 재시도
- 클라이언트가 요청을 보냈는데 응답을 받기 전에 네트워크가 끊기면, 클라이언트는 요청이 처리되었는지 알 수 없습니다.
- 멱등한 요청은 다시 보내도 결과가 같으므로 안전하게 재시도할 수 있습니다. HTTP 표준도 멱등한 요청은 통신 실패 시 자동으로 다시 보낼 수 있다고 정합니다.
- 멱등하지 않은 POST를 그대로 재시도하면 주문이나 결제가 두 번 생길 수 있습니다.
5-4 POST를 멱등하게 만드는 방법
- 멱등 키(Idempotency Key): 클라이언트가 요청마다 고유한 키를 만들어
Idempotency-Key헤더에 담아 보냅니다. 서버는 처리한 키와 결과를 저장해 두고, 같은 키로 다시 들어온 요청은 처리하지 않고 저장한 결과를 돌려줍니다. 결제 API에서 많이 씁니다. - 유니크 제약: 주문 번호처럼 중복되면 안 되는 값에 DB 유니크 제약을 걸어, 같은 요청이 두 번 들어와도 두 번째 저장이 실패하게 합니다.
6. HTTP 상태 코드
6-1 분류
- 1xx (정보): 요청을 받았고 처리를 계속한다는 뜻입니다. 웹소켓으로 프로토콜을 바꿀 때 쓰는 101 Switching Protocols가 대표적입니다.
- 2xx (성공): 요청을 정상적으로 처리했습니다.
- 3xx (리다이렉션): 요청을 마치려면 다른 URI로 다시 요청하거나 캐시를 써야 합니다.
- 4xx (클라이언트 오류): 요청에 문제가 있어 처리하지 못했습니다. 원인은 클라이언트 쪽에 있습니다.
- 5xx (서버 오류): 서버 쪽 문제로 요청을 처리하지 못했습니다.
6-2 자주 쓰는 코드
2xx
- 200 OK: 조회나 수정이 성공했고 응답 본문이 있습니다.
- 201 Created: 자원이 생성되었습니다. POST로 생성했을 때 쓰고, 새 자원의 URI를
Location헤더에 담습니다. - 204 No Content: 성공했지만 응답 본문이 없습니다. 삭제나 본문을 돌려주지 않는 수정에 씁니다.
POST /api/users → 201 Created
Location: /api/users/10
DELETE /api/users/10 → 204 No Content3xx
- 301 Moved Permanently / 302 Found: 자원이 다른 URI로 영구히 / 임시로 옮겨졌습니다.
- 304 Not Modified: 캐시해 둔 응답이 아직 유효하므로 그대로 써도 됩니다. 서버는 본문을 보내지 않습니다.
4xx
- 400 Bad Request: 요청 형식이 잘못되었거나 입력값 검증에 실패했습니다.
- 401 Unauthorized: 인증되지 않았습니다. 이름은 Unauthorized지만 뜻은 "인증 실패"입니다.
- 403 Forbidden: 인증은 되었지만 권한이 없습니다.
- 404 Not Found: 요청한 자원이 없습니다.
- 409 Conflict: 자원의 현재 상태와 충돌합니다. 이미 가입된 이메일로 가입하거나, 낙관적 락의 버전이 맞지 않을 때 많이 씁니다.
5xx
- 500 Internal Server Error: 서버 내부에서 예상하지 못한 오류가 났습니다.
- 502 Bad Gateway: Nginx 같은 중간 서버가 뒤의 애플리케이션 서버로부터 잘못된 응답을 받았습니다. 애플리케이션 서버가 내려가 있을 때 자주 봅니다.
- 503 Service Unavailable: 과부하나 점검으로 지금은 요청을 처리할 수 없습니다.
- 504 Gateway Timeout: 중간 서버가 뒤의 서버로부터 제시간에 응답을 받지 못했습니다.
6-3 결과가 비어 있을 때
- 목록 조회 결과가 없으면 200과 빈 배열(
[]) 을 반환하는 것이 일반적입니다. 컬렉션이라는 자원은 존재하고, 내용만 비어 있기 때문입니다. - 404는 요청한 자원 자체가 없을 때 씁니다.
/api/users/999처럼 경로가 가리키는 대상이 없는 경우입니다. - 204는 본문이 없다는 뜻이므로, 본문을 기대하고 파싱하는 클라이언트는 오류를 낼 수 있습니다.
- 어떤 코드를 쓸지는 클라이언트와 약속하고 API 문서에 적어 둡니다.
7. 응답 설계
7-1 엔티티를 그대로 응답하지 않는다
DB 테이블과 연결된 엔티티를 그대로 응답하지 않고, API마다 필요한 필드만 담은 응답 전용 객체(DTO)를 만들어 반환합니다.
- 보안: 비밀번호처럼 응답에 나가면 안 되는 필드까지 나갑니다. 화면에서 숨겨도 브라우저 개발자 도구로 응답 원문을 볼 수 있습니다.
- 유지보수: API 응답이 DB 구조에 묶입니다. 테이블 컬럼을 바꾸면 API 스펙이 함께 바뀌고, API 요구사항이 바뀌면 엔티티를 고쳐야 합니다.
- 응답 형식: 같은 자원이라도 API마다 필요한 필드가 다르므로, 응답 객체를 따로 두면 API마다 원하는 형식으로 가공할 수 있습니다.
7-2 에러 응답의 공통 형식
모든 API가 같은 형식으로 에러를 응답하게 합니다.
{
"status": 400,
"code": "INVALID_INPUT",
"message": "입력값이 올바르지 않습니다.",
"errors": [
{ "field": "email", "reason": "이메일 형식이 아닙니다." }
]
}
- status: HTTP 상태 코드와 같은 값입니다.
- code: 서비스가 정한 에러 코드입니다. 상태 코드만으로는 400 안의 여러 경우(이메일 형식 오류, 비밀번호 규칙 위반 등)를 구분할 수 없으므로, 클라이언트가 이 코드로 분기합니다.
- message: 사람이 읽을 설명입니다.
- errors: 입력값 검증에 실패한 필드 목록입니다.
- 내부 정보를 담지 않습니다. 500 응답에 스택 트레이스나 SQL을 담으면 공격자에게 서버 구조를 알려 주게 됩니다.
- 에러 응답도 API 문서에 적습니다. 성공 응답만 문서에 있으면 클라이언트는 실패를 어떻게 처리할지 알 수 없습니다.
8. 페이지네이션
목록을 나눠서 조회하는 방식은 크게 오프셋 방식과 커서 방식이 있습니다.
8-1 오프셋 방식
페이지 번호와 크기로 앞에서 몇 개를 건너뛸지 정하는 방식입니다.
GET /api/playlists?page=5000&size=20SELECT * FROM playlist ORDER BY id DESC LIMIT 20 OFFSET 100000;
- 원하는 페이지로 바로 이동할 수 있습니다. 페이지 번호만 바꿔 요청하면 됩니다.
- 뒤 페이지로 갈수록 느려집니다. DB는
OFFSET 100000을 처리할 때 정렬 순서대로 100,000개 행을 읽고 버린 뒤 다음 20개를 돌려줍니다. 건너뛸 행의 위치로 바로 갈 방법이 없기 때문입니다. - 항목이 밀립니다. 1페이지를 본 뒤 새 데이터가 하나 추가되면, 2페이지 첫 항목으로 1페이지 마지막 항목이 다시 나옵니다. 삭제되면 반대로 하나를 건너뜁니다.
- 전체 개수 조회 비용이 듭니다. 전체 페이지 수를 보여 주려면
COUNT(*)쿼리가 따로 필요하고, 이 쿼리도 데이터가 많으면 느립니다.
8-2 커서 방식
마지막으로 받은 항목의 정렬 기준 값(커서)을 기준으로 다음 항목을 조회하는 방식입니다.
GET /api/playlists?cursor=98500&size=20SELECT * FROM playlist WHERE id < 98500 ORDER BY id DESC LIMIT 20;
- 다음 요청은 "이 커서 값 다음부터 20개"를 요청합니다.
- 페이지 깊이와 관계없이 속도가 같습니다.
id에 인덱스가 있으면 DB는 인덱스에서 98500의 위치를 바로 찾아 20개만 읽습니다. - 항목이 밀리지 않습니다. 기준 값 이후를 읽으므로, 앞쪽에 데이터가 추가되어도 항목이 중복되거나 빠지지 않습니다.
- 특정 페이지로 바로 갈 수 없습니다. 다음 페이지로만 이동할 수 있습니다.
8-3 커서 설계에서 주의할 점
정렬 기준이 유일해야 합니다. 별점처럼 같은 값이 많은 기준으로 정렬하면 별점만으로는 다음 위치를 정할 수 없습니다. ID를 보조 기준으로 붙여 (별점, ID) 순서로 정렬하고, 커서에도 두 값을 함께 담습니다.
정렬 기준 컬럼에 인덱스를 둡니다. 보조 기준까지 포함한 복합 인덱스가 있어야 커서 위치를 바로 찾습니다.
응답에 다음 요청에 필요한 정보를 담습니다.
hasNext는 요청한 개수보다 하나 더 조회해 보고, 결과가 더 있으면true로 둡니다.{ "content": [ ... ], "nextCursor": "98480", "hasNext": true }전체 개수는 필요할 때만 넣습니다. 무한 스크롤처럼 전체 개수가 필요 없는 화면이면 넣지 않고
COUNT(*)쿼리를 줄입니다. 넣는다면 전체 데이터 개수인지 이번 응답의 개수인지 이름과 문서로 분명히 합니다.
8-4 어떤 것을 고르나
- 오프셋 방식: 관리자 화면이나 게시판처럼 페이지 번호로 이동해야 하고, 데이터 양이 아주 크지 않은 경우에 씁니다.
- 커서 방식: 피드, 알림 목록처럼 데이터가 계속 쌓이고 다음 항목을 이어서 보는 경우에 씁니다.
9. API 버전 관리
- 언제 버전을 올리나: 기존 클라이언트를 깨뜨리는 변경에만 올립니다. 필드 삭제나 이름 변경, 타입 변경, 필수 파라미터 추가가 여기에 해당합니다. 선택 필드나 새 API를 추가하는 것은 기존 클라이언트가 그대로 동작하므로 버전을 올리지 않습니다.
- URI 버전:
/api/v1/users처럼 경로에 넣습니다. 가장 흔하고, 어떤 버전을 호출하는지 URL만 보고 알 수 있습니다. 대신 같은 자원의 URI가 버전마다 달라집니다. - 헤더 버전:
Accept: application/vnd.example.v2+json이나 별도 헤더로 버전을 보냅니다. URI는 그대로 유지되지만, 브라우저 주소창만으로는 테스트하기 어렵습니다. - 새 버전을 내도 구 버전을 바로 내리지 않고, 클라이언트가 옮겨 갈 기간을 둡니다.
10. REST 성숙도 모델과 HATEOAS
10-1 리처드슨 성숙도 모델
레너드 리처드슨(Leonard Richardson)이 API가 REST를 얼마나 따르는지를 네 단계로 나눈 모델입니다.
- 0단계: HTTP를 전송 수단으로만 씁니다. 하나의 URI(
POST /api)로 모든 요청을 받고, 본문에 행위를 담습니다. - 1단계, 자원: 자원마다 URI를 나눕니다(
/users,/orders). 메서드는 아직 구분하지 않습니다. - 2단계, HTTP 메서드: 메서드와 상태 코드를 의미에 맞게 씁니다. 실무에서 REST API라고 부르는 것은 대부분 여기까지입니다.
- 3단계, 하이퍼미디어: 응답에 다음 행위로 가는 링크를 담습니다(HATEOAS).
10-2 HATEOAS
HATEOAS(Hypermedia As The Engine Of Application State)는 응답에 관련 자원과 가능한 행위의 링크를 담아, 클라이언트가 URI를 미리 알지 않고 링크를 따라 이동하게 하는 것입니다.
{
"id": 1,
"nickname": "kim",
"_links": {
"self": { "href": "/api/users/1" },
"orders": { "href": "/api/users/1/orders" }
}
}
- 자원의 상태에 따라 링크가 달라집니다. 예를 들어 결제 전 주문에는 취소 링크가 있고, 배송이 시작된 주문에는 없습니다. 클라이언트는 링크가 있는지로 할 수 있는 행위를 판단합니다.
- 장점: 서버가 URI를 바꿔도 링크를 따라가는 클라이언트는 고칠 필요가 없습니다.
- 실무에서 잘 쓰지 않는 이유: 프론트엔드는 보통 API 문서를 보고 URI를 코드에 직접 적으므로 링크의 이점이 적고, 응답이 커지고 구현이 복잡해집니다.
- 필딩은 하이퍼미디어가 없는 API는 REST가 아니라고 말했습니다. 그래서 2단계까지의 API를 엄밀하게는 "HTTP API"라고 부르기도 합니다.