내가 알던 REST API는 새 발의 피였떵
백엔드를 시작하고 얼마 전 만든 api에서 rest api 주소정책을 다시 공부해보라는 코드리뷰를 받았다.
코드를 공개하긴 좀 그렇지만 A에서 B의 뭐를 불러온다. 이런거였음
개발 공부를 처음 시작 할 때 html, css, javascript 다음에 배운게 rest api였고, 이제 REST API를 더 심도있게 생각해봐야 되겠다고 생각도 들었다. 그리고 이번에 공부하면서, 간단하게 알고있었던 POST, PUT, DELETE .. 또는 ststus code의 의미의 쓰임새 뿐만 아니라, response를 어떻게 보내주냐도 restful해지는 것임을 알게되었다.
HATEOAS는 처음 들어 본 개념이었고, JSON을 통한 에러 응답처리 또한 실무에서 사용하고있었지만 이 개념에 들어있는지는 처음 알게되었다.
많은 사람들이 정리해 둔 자료가 많아서, 내가 몰랐던 부분과 알았지만 잘 못하고 있었던 부분 , 나중에 적용하고 싶은 부분위주로 정리해보기로 한다.
1. REST의 3요소
구성 요소 내용 표현 방법 예
자원 (Resource) |
자원 | HTTP URI | /members/{1}, /member/ |
행위 (Verb) |
자원에 대한 행위 | HTTP Method | POST, GET, DELETE, PUT |
표현(Representations) | 자원에 대한 행위의 내용 (즉, 요청에 대한 Body) | HTTP Message Payload (JSON, XML, TEXT, RSS 등) | { member-id:”82370”, member-name:”홍길동“, member-org:”10100”, member-location:”11010” } |
2. 명사를 통한 리소스 식별
"path에 다음과 같은 동사를 사용하는 것을 지양한다." == "행위 (method)를 URL에 포함하지 않는다!"
-> (내가 한거 1 😅)
동사 (X) - 이미 있기 때문에 | 명사 (O) | |
GET | /getAllUser | /user |
GET | /getUserById | /user/:id |
POST | /createNewUser | /user |
PATCH / PUT | /updateUser | /user/:id |
DELETE | /deleteUser | /user/:id |
PUT과 PATCH
- PUT 자원 전체를 대체(수정)하는 경우 사용한다.
- 요청을 일부만 보낸 경우 나머지는 default 값으로 수정되는게 원칙이나, 대부분 전체를 다 보낸다.
- PUT은 바뀌지 않은 속성도 보내야한다.
- PATCH 자원 일부를 수정할 때 사용
3. 서브 URL 표현식을 통해 세부 표현 ⭐️ (코드 리뷰 받은 부분)
API 내부에 다른 리소스와 관계가 있을 때, 서브 리소스 표현으로 그 관계를 표현한다.
ex) id가 123인 유저의 메세지를 반환
→ GET /users/123/message
ex) id가 123인 유저의 id가 abc인 메세지를 반환
→ GET /users/123/message/abc
POST, PUT(전체 업데이트), PATCH(부분적 업데이트), DELETE 도 같은 방식으로 관계 표현
-> NestJS를 쓰면 controller 맨 위에 controller 이름이 있어서 이중으로 관계가 있을 때 실수를 더 많이하게 된다. 이걸 주의하자!!
4. 검색, 정렬, 필터링, 페이징을 위한 규칙 사용
Q -> 근데 사실 내부적으로는 같은 것 아닌가? 라는 생각이 들었다. 왜냐하면 결국 아래 예시로 보면 sort, search, page, 등은 결국 단어에 불과하니까 코드는 똑같이 동작하는거 아니야? 라는 생각.
A -> 하지만 REST에서 권장하는 방식으로 이렇게 쓰라고 하는 이유는 REST에서 권장했기 때문임. 왜냐하면 백엔드 개발자가 아닌 프론트엔드 개발자 또는 데이터를 관리하는 사람들이 이 URI를 보고 쉽게 이해할 수 있도록 하는것이 목적이기 때문.
지금 REST API 주소정책을 배우고있음을 잊지말자 ㅋ
상태 코드 | 설명 |
Sorting | - 예를들어 클라이언트가 정렬된 회사 목록을 가져오려는 경우 다음의 예처럼 처리 - e.g. GET /companies?sort=rank_asc |
Filtering | - 데이터셋의 데이터를 필터링 할 때, 다양한 쿼리 파라미터를 통해 필터링 처리 가능 - e.g GET /companies?category=banking&location=india (컴퍼니의 카테고리를 은행으로 정하고 은행이 위치한 장소를 인도로 필터 처리) |
Searching | - 검색은 다음의 예제와 같이 표현 - e.g. GET /companies?search=Digital |
Pagination | - 페이징 처리 예제 - e.g. Eg. GET /companies?page=23 |
5. API 버전 관리 ⭐️
새로 알게 된 사실
버전을 url로 사용하고있는 url을 캡쳐해왔다. github docs에서는 버전을 날짜로 사용하고있다. 신기하군
http(s)://{Domain name (:Port #)}/{A value indicating REST API}/{API version}/{path for identifying a resource}
- URI versioning
- http://api.test.com/v1 (O)
- http://apiv1.test.com (X)
- Accept header
- URL Versioning 개발 가이드
- 개발 코드에 버저닝 정보를 관리하지 않는다. -> 예시 밑에
- 개발 프로젝트 폴더의 버저닝은 VCS(Version Control System)를 이용한다.
- v1(v1 branch), v2(master branch)
- 웹 서버의 reverse-proxy 기능을 활용한다
- 웹 APP 서버의 라우팅은 버저닝을 제외하고 개발한다. /users, /posts
- http://api.test.com/v1 -> (reverse-proxy) -> 웹 APP /
- 시나리오 case(Node.js express server)
- v1 process
- app-name: v1
- port: 3001
- dir: /home/v1
- Apache ProxyPassReverse “/v1” “http://127.0.0.1:3001”
- Nginx location /v1 {proxy_pass http://127.0.0.1:3001}
- v2 process
- app-name: v2
- port: 3002
- dir: /home/v2
- Apache ProxyPassReverse “/v2” “http://127.0.0.1:3002”
- Nginx location /v2 {proxy_pass http://127.0.0.1:3002}
- v1 process
- API 버저닝 여부에 관계없이 프로젝트 구조가 변경되지 않는다.
// bad 👎
routes
- /v1
- /users
- /posts
- /v2
- /users
- /posts
- ...
// good 👍
routes
- /users
- /posts
6. HATEOAS(Hypermedia as the Engine of Application State)
REST API는 요청-응답 구조로 이루어져있고, 응답의 내용이 단순하다.
그와중에 api path가 user/:id 였는데, user/personal/:id 로 바뀌면 어떻게 될까?
클라이언트에서 다 바꿔줘야한다. (프론트의 눈물)
그런데 response로 client에서 필요로 하는 데이터 뿐만이 아니라, 참조되는 URL까지 LINK로 제공하게 된다면 그러지 않아도 된다.
예를 들어 내가 A 페이지 -> B -> C 이렇게 이동을 할 예정이라면 백엔드에서 그 A, B, C 페이지의 정보를 전달해주면 되는것이다.
그럼 만약 내가 토스의 프론트엔드 개발자라면 (그럼 좋겠다🥲) response로 온 그 정보의 링크를 버튼의 props로 전달해서 전달해주면 유저가 다음 페이지로 뿅뿅 이동할 수 있는 것이다.
- 구성요소
- 변경 될 리소스 상태 관계 rel
- self: 현재 URL자신
- 요청 URL href
- 요청 Method method
-> 예시 코드
201 Created
{
"id": 1,
"name": "hak",
"createdAt": "2018-07-04 14:00:00"
"links": [
{
"rel": "self",
"href": "http://api.test.com/users/1",
"method": "GET"
},
{
"rel": "delete",
"href": "http://api.test.com/users/1",
"method": "DELETE"
},
{
"rel": "update",
"href": "http://api.test.com/users/1",
"method": "PATCH",
"more_info": "http://api.test.com/docs/user-update"
"body": {
"name": "{The value to be modified}"
}
},
{
"rel": "user.posts",
"href": "http://api.test.com/users/1/posts",
"method": "GET"
}
]
}
이 방법만 있는게 아니다.
응답 예제
- HTTP Header의 Link 속성을 이용한다.
- HATEOAS로 응답한다. (이게 위에꺼)
- Link, HATEOAS 모두 사용한다.
7. JSON을 통한 에러 응답 처리
(그래서 Nest에서 exceptions 설정하면 이런 형식으루 나왔구나? 뭔가 역시 하나도 그냥 만들어지는건 없는듯 하구만..)
API는 항상 자체 필드 집합에서 오류 메시지를 반환하도록 적극 권장하고 JSON 오류 본문은 개발자에게 유용한 오류 메시지, 고유한 오류 코드(문서등을 통해 자세한 내용을 찾을 수 있게해야 함) 및 가능한 자세한 설명 등 몇 가지 사항을 제공해야 한다.
{
"코드": 1234,
"message" : "예기지 않은 오류가 발생했습니다 .",
"description" : "오류에 대한 자세한 내용은 xxxx를 참조."
}
참고 curl 설치하기
https://www.lesstif.com/software-architect/curl-http-get-post-rest-api-14745703.html
검색하면 다나와서 맨뒤로 뺀 주요 HTTP 상태코드
2xx
200: ok
- GET: 리소스를 불러와서 메시지 바디에 전송되었습니다.
- HEAD: 개체 해더가 메시지 바디에 있습니다.
- PUT or POST: 수행 결과에 대한 리소스가 메시지 바디에 전송되었습니다.
201: created
- 리소스 생성에 성공한 응답 코드
- 일반적으로 Post, Put 요청 이후에 사용
202: accepted
- 요청 접수 O, 처리가 완료되지 않음
- 요청 접수 후 N시간 뒤에 배치 프로세스가 요청을 처리
204: no content -> refactor todo
- 서버가 요청을 성공적으로 수행했지만 응답으로 본문에 보낼 데이터가 없음
- save 버튼 → 굳이 저장 성공에 대한 응답 없어도 된다.
3xx
300: multiple choice
- 요청에 의해 하나 이상의 응답이 가능하다.
301: move permanently
- 요청한 리소스의 URL가 변경되었다는 의미
- 응답에서 새로운 URI가 주어질 것 예상할 수 있음
302: found
- 요청한 리소스의 URI가 일시적으로 변경.
- 새롭게 변경된 URI는 나중에 만들어 질 수 있으므로 클라이언트는 향후 요청에서도 동일한 URI를 사용해야 함.
303: see_other
- 클라이언트가 요청한 리소스를 다른 URI에서 GET 요청을 통해 얻어야 할 경우, 서버가 클라이언트로 직접 보내는 응답.
4xx
400: bad request
- 잘못된 문법으로 서버가 요청을 이해할 수 없음
401: unauthorized
- 인증되지 않은 사용자
403: forbidden
- 클라이언트가 접근할 권리 없음. 미승인!
- 401과 다른 점은 서버가 클라이언트가 누구인지 알고 있다.
404: not found
- 리소스를 찾을 수 없다. 브라우저에서는 알려지지 않은 URL을 의미합니다.
405: method not allowed
- 클라이언트가 요청한 리소스에서는 사용 불가능한 메소드를 이용했을 경우 사용.
429: too many requests
- 사용자가 지정된 시간에 너무 많은 요청을 보낼 때 (rating_limiting)
5xx
500: internal server errer