정상우
hELLO.
정상우
전체 방문자
342,471
오늘
800
어제
1,384
  • hELLO. (120)
    • 컴퓨터과학 (4)
      • 알고리즘 & 자료구조 (4)
    • 언어 & 프레임워크 (63)
      • Go (23)
      • PHP & Laravel (40)
    • 웹 (7)
    • 블록체인 (12)
      • 메인넷 (9)
      • 암호화폐 플랫폼 (3)
    • 포트폴리오 (10)
    • 칼럼 (19)
      • 에세이 (4)
      • 개발자스럽게 살기 (13)
      • 회고 (2)
    • 티스토리 (5)

블로그 메뉴

  • ⚡ 개발자 이력서
  • 🌟 깃허브
  • 💻 강의
  • ✨ 예제코드
  • ⭐ 브런치
  • 태그 클라우드
  • 방명록

공지사항

  • 2차 도메인을 설정했습니다 ✨

인기 글

  • [Laravel] 라라벨 프레임워크⋯
    2021.06.10
    [Laravel] 라라벨 프레임워크⋯
  • 'REST' 를 보다 'RESTful' 하게⋯
    2021.08.14
    'REST' 를 보다 'RESTful' 하게⋯
  • 암호화폐 트레이딩 봇을 만들었⋯
    2021.05.12
    암호화폐 트레이딩 봇을 만들었⋯
  • JWT(JSON Web Token)의 개념부⋯
    2021.07.29
    JWT(JSON Web Token)의 개념부⋯
  • 깃허브를 포트폴리오로 쓰려면⋯
    2021.12.25
    깃허브를 포트폴리오로 쓰려면⋯

태그

  • 포트폴리오
  • go
  • 개발
  • 프로그래머스
  • Algorithm
  • 블록체인
  • 개발 리뷰
  • 코딩테스트
  • 라라벨
  • php

최근 댓글

  • 감사합니다 ~~ :)
    정상우
  • 고맙습니다 :)
    정상우
  • 자료 받으면서 원래 댓글 잘 안⋯
    뷰스토리_
  • 다크모드 지원하는 스킨 찾고⋯
    PilTok
  • 고맙습니다 ㅎㅎ 그림은 적당히⋯
    정상우

최근 글

  • 개발자와 엔지니어, 그 사이에서
    2022.05.10
    개발자와 엔지니어, 그 사이에서
  • 아임포트(Iamport)로 결제기능⋯
    2022.04.03
    아임포트(Iamport)로 결제기능⋯
  • 아임포트(Iamport)로 결제기능⋯
    2022.04.01
    아임포트(Iamport)로 결제기능⋯
  • [Laravel] 카페24 호스팅에 라⋯
    2022.03.29
    [Laravel] 카페24 호스팅에 라⋯
  • 2021년 회고―, 성찰
    2021.12.31
    2021년 회고―, 성찰

티스토리

hELLO · Designed By 정상우.
정상우

hELLO.

'REST' 를 보다 'RESTful' 하게 API 만들기
웹

'REST' 를 보다 'RESTful' 하게 API 만들기

2021. 8. 14. 17:00

REST API

인증 파트가 어느정도 마무리되고 써볼 글은 API 에 대한 이야기다. 가장 처음, REST(Representational State Transfer) API 에 대한 이야기를 해보고자 한다. REST API 는 SPA(Single Page Application) 방식으로 개발된 프론트엔드에서 백엔드의 데이터를 가져올 때 가장 많이 사용되는 자원(Resource) 처리방식이다. REST API 를 따르는 API 가 세상에는 많이 존재하고 있으므로 몇 가지 규칙만 알고있으면 다른 서비스의 API 를 사용하는 것에 거부감을 느끼지는 않을 것이다.

 

REST API 의 가장 두 가지 특징으로는 URI 로 자원(Resource)을 요청하여 특정 형태로 표현(Representation)한다는 것과 HTTP Method 를 적극적으로 활용하여 행위(Verb)를 나타낸다는 점이다. 예를 들어 GET /users 는 모든 user 의 정보를 응답으로 달라는 이야기가 되며 DELETE /user/1 은 user id: 1 에 해당하는 user 를 제거하라는 의미가 된다. REST API 의 요청으로 나오는 응답은 대체로 JSON(Javascript Object Notation)으로 표현되므로 따라서 사람이 읽기 쉽다.

 

REST API 는 무상태(Stateless) 환경에서 동작하는 것을 전제로 한다. 따라서 보안 및 인증에 대해서는 JWT(JSON Web Token), OAuth 와 같은 토큰 인증이 사용되며 상태 유지를 위한 세션(Sessions)은 사용하지 않는다. 또한 보안을 위해서 HTTPS 를 사용하는 것을 잊어서는 안 된다. HTTP 요청/응답의 단순한 사이클을 거치기 때문에 프론트엔드가 어떤 환경에서 동작하는지에 대한 의존도가 낮으며 자원을 자주 조회해야 한다면 ETag, Last-Modified 헤더를 통한 캐싱(Caching)도 가능하다. 

 

이러한 REST API 를 설계할 때 강제하는 표준은 없지만, 보다 'ful' 하게 설계하는 방법은 분명하게 존재한다. 따라서 이 포스트에서는 REST API 를 보다 'ful'(RESTful) 하게 만들 수 있도록 가이드를 제공한다.

'REST' 를 보다 'RESTful' 하게

REST API 는 기본적으로 URI 로 자원을 표현하고, 자원에 대한 행위는 HTTP Method 를 사용한다는 점이다. 이 부분은 핵심적인 부분이다. 따라서 다른 부분보다 가장 우선시 해야 하고, 일부는 선택적으로 제공한다. 실제로 REST API 를 제공하는 이름이 알려진 서비스들도 규칙을 완전히 준수하는 것은 아니며 기능을 제공하지 않는 경우도 많다.

URI 규칙

REST API 가 과연 'ful' 한지 볼 때 처음 살펴보면 좋은 것은 자원에 대한 URI 규칙이 제대로 적용되었는지 살펴보는 것이다. 아래의 사항이 지켜졌는지 살펴볼 필요가 있다.

규칙 나쁨 좋음
마지막이 / 로 끝나서는 안 된다. http://api.test.com/users/ http://api.test.com/users
_ 대신 - 를 사용한다. http://api.test.com/tag/rest_api http://api.test.com/tag/rest-api
소문자로 구성한다. http://api.test.com/tag/REST-API http://api.test.com/tag/rest-api
동사(Verb)는 포함하지 않고, HTTP Method 로 대체한다. POST http://api.test.com/delete-user/1 
POST http://api.test.com/delete/user/1
DELETE http://api.test.com/user/1
파일 확장자 표시하지 않기 http://api.test.com/users/1/profile.png http://api.test.com/users/1/profile (Accept 사용) 

Accept: image/png

리소스 관계 표현하기

자원 간 관계가 있는 경우가 많다. http://api.test.com/users/1/posts 처럼 표현할 수 있는데, 예를 들어 한 명의 유저는 다수의 게시글을 가지고 있는 경우가 대표적이다. 이 경우 경로 상에서 / 를 사용하여 표현할 수 있는데, 예시에서 users/1 는 다수의 유저 중에서 id: 1 에 해당하는 유저를 나타내고, 그 내부에 있는 posts 는 해당 유저에 속한 게시글들을 의미한다.

 

경로에서 posts 와 같이 복수형으로 쓰인 부분은 같은 계열의 여러 자원을 가지고 있는 컬렉션(Collection)이라하고, 1 과 같이 숫자로 쓰여있거나 단수형으로 나타낸 경우 컬렉션에 포함한 자원 중 하나를 나타내는 도큐먼트(Document)라고 한다. 다른 일부 권고사항이 설령 지켜지지 않더라도 URI 규칙의 준수여부에 따라 RESTful 인지 아닌지 분명하게 구분이 가능하다.

Header 설정하기

헤더를 설정하는 부분은 필수적이지는 않지만, 응답으로 돌려줄 때 프론트엔드에서 참고하기 좋을 것이다.

헤더 설명
Content-Location 예를 들어 POST /posts {"title": "Hello, world"} 와 같은 요청으로 유저를 생성했을 때 Content-Location 을 사용하여 리소스의 위치를 표현할 수 있다. 리소스 생성으로 발생한 자원 위치는 항상 다를 것이기 때문이다. 이 부분은 아래에서 나올 HATEOAS 으로 대체할 수 있다.

Content-Location: /posts/1
Content-Type 서비스에 따라 xml 을 제공하는 경우도 있지만, 어지간하면 json 으로만 사용하여 API 사용자가 일관성있게 사용할 수 있도록 유도하자.

Content-Type: application/json
Retry-After API 요청을 너무 많이 한 경우 429 Too Many Requests 응답과 함께 사용한다. 이 부분은 공격자가 API 서버를 사용하여 서비스에 과부하를 일으키려 할 때 조금이나마 보호막이 되어줄 수 있다. 물론, 이 기능만으로 네트워크에서 오는 공격을 막을 수만은 없을 것이다.

Retry-After: 3600

HTTP 메서드 적극적으로 사용하기

이 부분은 URI 규칙과 함께 가장 중요한 부분 중 하나이며 지켜져야 한다. REST API 는 URI 에 동사를 직접 명시하는 대신 HTTP Method 로 무엇을 할지 명시한다. 가장 많이 사용되는 메서드는 GET, POST, PUT, DELETE 이며 PUT 대신 PATCH 를 사용하는 경우도 있으고 완성도 높은 API 제공을 위해 추가적으로 OPTIONS, HEAD 를 제공하기도 한다.

/posts GET POST PUT DELETE
모든 포스트를 얻어온다. 새로운 포스트를 생성한다. 405 Method Not Allowed 모든 포스트를 제거한다.
/posts/1 GET POST PUT DELETE
id: 1 에 해당하는 포스트를 얻어온다. 405 Method Not Allowed id: 1 에 해당하는 포스트를 수정한다. id: 1 에 해당하는 포스트를 제거한다.

위의 표에서 PUT 은 전체 자원을 수정하기 위해 사용한다, 하지만 언급되지 않은 PATCH 는 일부 자원을 수정하기에 적합한 메서드다. 물론, 이것이 강제되는 것은 아니다. 그 외에 OPTIONS 는 자원에 대해 사용가능한 메서드를 반환하고, HEAD 는 Body 는 제외한 Header 만 반환한다.

Form

예를 들어 Post 에 대한 CRUD(Create, Read, Update, Delete)작업이 있다고 생각해보자. 이러한 작업은 HTML Form 을 통해 구성되는 경우가 많아서 뷰와 함께 제공되곤 한다. Form 을 포함한 구성 또한 URI 규칙을 통해 RESTful 하게 구성이 가능하다.

HTTP 메서드 URI 설명
GET /posts 모든 포스트를 조회한다.
GET /posts/create 포스트를 생성하기 위한 Form
POST /posts 포스트를 생성한다.
GET /posts/:id 포스트를 조회한다.
GET /posts/:id/edit 포스트를 수정하기 위한 Form
PUT/PATCH /posts/:id 포스트를 수정한다.
DELETE /posts/:id 포스트를 삭제한다.

메서드 스푸핑

다만 여기서 의문이 들 수 있는 부분은, HTML Form 의 경우는 일반적으로 GET, POST 만을 지원하고 PUT/PATCH, DELETE 는 지원하지 않는데, 어떻게 지원하지 않는 메서드를 사용하여 서버에 전달하냐는 것이다. 이를 처리하는 것은 프레임워크의 구현마다 다를 수 있는데, _method 라는 이름을 가진 필드에 메서드의 이름을 전달하여 서버가 해당 메서드인 것처럼 인지하도록 속인다. 메서드 스푸핑(Method Spoofing)이라 한다.

<form method="POST" action="/posts/1">
	<input type="hidden" name="_method" value="PUT"> 
</form>

HTTP 상태코드

RESTful API 를 구성하면서 고민하는 부분 중 하나는 자원을 생성하거나, 수정, 삭제했을 때 어떤 상태코드로 반환하냐는 것인데, 아래의 표를 참고하면 그에 대한 해답을 얻어낼 수 있다.

2xx

상태코드 설명
200 OK 요청이 올바르게 수행되었음.
201 Created 서버가 새로운 리소스를 생성했거나 수정했음. (POST, PUT)
204 No Content 리소스가 더 이상 존재하지 않으므로 응답할 데이터가 없음 (HTTP Body 가 없음). (DELETE)

4xx

상태코드 설명
400 Bad Request 요청이 잘못되었음. (Error 메시지 등의 에러가 발생한 이유를 같이 표현해야 하고, 특정 코드로 표현한다면 이를 문서에 정리해두고 별도의 필드로 링크 전달.)

400 Bad Reqeust {"message": "Parameter is not valid"}
400 Bad Request {"code": -765", "more_info":
https://api.test.com/errors/-765"}
401 Unauthorized 인증이 진행되지 않았음. 로그인이 되지 않은 경우를 말한다.
403 Forbidden 권한이 인가되지 않았음. 로그인이 되었으나 해당 자원에 대한 처리에 대한 권한이 없음을 말한다.
404 Not Found 자원을 찾을 수 없음. /posts/2 라는 요청을 했을 때 그에 해당하는 자원이 존재하지 않는 경우 반환.
405 Method Not Allowed 자원에 대한 특정 메서드를 지원하지 않음. 예를 들면, POST /posts/1 를 요청한 경우에 해당한다. 자원을 찾을 수 없다는 404 Not Found 와 헷갈려서는 안 된다.
409 Conflict 비지니스 로직상 요청을 처리하지 못한 경우.
429 Too Many Requests 요청을 너무 많이한 경우 (Retry-After 와 함께 사용)

5xx

500 과 관련된 에러는 서버 내부에서 발생한 에러이므로 클라이언트에 표시되지 않도록 해야한다. 파라매터의 필수값 등을 확인하고 유효한지 확인해야한다.

HATEOAS

HATEOAS(Hypermedia as the Engine of Application State)는 단어 자체를 처음 들으면 무슨 말인지 잘 모르겠지만, 간단하게 이야기하자면 어떤 자원에 대해 처리를 했을 때, 그에 대한 응답으로 프론트엔드에서 처리하기 용이하도록 자원에 대한 Links 함께 제공하자는 이야기다. Content-Location 을 대신할 수 있다.

 

예를 들어 POST /posts {"title":" Hello, world"} 요청을 보냈다고 가정해보자. 이 요청은 새로운 포스트를 만들라는 의미이며 아래와 같이 링크정보를 포함하여 응답할 수 있게된다. 이를 사용하면 RESTful API 스타일을 따르는 자원 접근방식을 통해 클라이언트가 자원을 조작할 수 있게되고 클라이언트에서 URL 을 직접 구성할 필요가 사라진다. 그저 응답에서 제공하는 정보만 참조하면 되기 때문이다.

201 Created
{
    "id": 1,
    "title": "Hello, world",
    "createdAt": "2021-08-14 12:00:00",
    "links": [
        {
            "rel": "self",
            "href": "http://api.test.com/posts/1",
            "method": "GET"
        }
    ]
 }

rel 부분은 self 를 제외하곤 임의로 정해서 사용할 수 있으며 href, method 이외에도 더 자세한 정보를 제공하는 링크인 more_info, 요청 예시를 제공하는 body 등을 사용할 수 있다. 이는 정해진게 아니며 내부적으로 약속에 의해 추가될 수 있다. 또한 링크를 표현할 때는 일관성있게 표현해야 한다.

페이징

REST API 를 통해 페이징을 처리하는 것은 자주 있는 일이다. GET /users 를 조회함에 있어 모든 포스트가 다 조회된다면 데이터가 많을수록 응답을 하기에 아주 오랜시간이 걸릴 것이다. 이러한 페이징을 처리할 때에는 Link Header 를 사용하거나 HATEOAS 를 사용하여 표현하는 것이 좋은 방법이다. 페이징 요청을 던지기 위한 예제 포맷은 아래와 같다.

예제포맷 설명
/users?offset=0&limit=10 SQL 문법에서 사용하는 OFFSET, LIMIT 를 그대로 사용하여 보다 손쉽게 작성할 수 있다는 것이 장점. 요청할 때 처음시작 부분을 지정하여 요청을 하므로 어플리케이션 내부에서는 어디까지 페이징을 했는지 계산하지 않는다는 점이 특징이다.
/users?limit=10 어플리케이션 내부에서 어디까지 페이징을 했었는지 계산하고 추가적인 요청이 있을 때 LIMIT 에 대한 것 까지만 응답한다.

표에 나와있는 것처럼 요청하면 아래와 같은 응답을 던져줄 수 있다. Link Header 와 HATEOAS 를 둘 다 사용하여 표현한 것이다. 둘 중에 하나만 사용해도 상관없다.

200 OK

Link: <https://api.test.com/users?offset=10&limit=10>; rel="next",
 <https://api.test.com/users?offset=50&limit=10>; rel="last",
 <https://api.test.com/users?offset=0&limit=10>; rel="first",
 <https://api.test.com/users?offset=0&limit=0>; rel="prev",
{
    {1, ...},
    {2, ...},
    ...
    {10,...},
    "links": [
        {
            "rel": "next",
            "method": "GET",
            "link": "https://api.test.com/users?offset=10&limit=10"
        },
        {
            "rel": "last",
            "method": "GET",
            "link": "https://api.test.com/users?offset=50&limit=10"
        },
        {
            "rel": "first",
            "method": "GET",
            "link": "https://api.test.com/users?offset=0&limit=10"
        },
        {
            "rel": "prev",
            "method": "GET",
            "link": "https://api.test.com/users?offset=0&limit=0"
        }
    ]
}

정렬, 필터링, 필드 선택

여기에서는 REST API 에서 GET 요청에 대해 자원을 정렬하고, 필터링하고, 선택하는 방법에 대해 이야기해본다. 응답의 결과는 SQL 로 데이터베이스와 요청한 것과 유사하다. 물론 이부분은 꼭 정해져있는 포맷이라기 보다는 구현에 따라 달리 표현할 수도 있다.

종류 예제포맷 설명
정렬 /posts?order=-title, id
/posts?sort_by=title&order=desc
/posts?sort=title:desc, id:asc
- 부호가 붙어있는 것은 내림차순하고, 그 외에는 오름차순으로 정렬. 그 외에 다른 표현으로 사용할 수 있다.
필터링 /posts?filter=id: 1 AND title: Hello, world
/posts?id=1&title=Hello, world
/posts?id[eq]=1&title[eq]=Hello, world
/posts?filter={
  "id": {"op": "=", "value": "1"},
  "title": {"op": "=", "value": "Hello, world"}
}
필터링을 하는 것은 여러가지 포맷이 존재할 수 있지만, Query 와 비슷한 형태로 제공할 수 있다면 다채롭게 사용자에게 검색기능을 제공할 수 있다.
필드 선택 /posts?-fields=title
/posts?fields=title, id
fields 파라매터에 나열된 필드만 응답하고, -fields 로 표현된 것은 응답에서 제외하고 응답. 존재하지 않는 필드를 요청하는 경우 무시한다.

버전 관리하기

REST API 의 버전을 사용자에게 명시하기 위한 방법으로는 URL, Accept Header, Accept-Version 로 처리하는 세 가지 방법이 있다. 

종류 예제포맷
URL http://api.test.com/v1
http://service1.test.com/api/v1
Accept Accept: application/vnd.test.v1+json
Accept: application/vnd.test+json;version=1.0
Accept-version Accept-version: v1

만약 URL Versioning 을 사용하여 구성할 때 어플리케이션 라우트에 v1, v2 와 같은 버전을 명시해서 처리하기 보다는 http://api.test.com/v1 에 대한 요청이 들어오면 버전에 해당하는 API 서버로 연결해서 요청하는 것이 좋다. 이를 때면 http://api.test.com/v1 요청에 대해서는 http://127.0.0.1:3001 서버로 요청한다. 따라서 어플리케이션 라우트에는 v1, v2 와 같은 버전에 대한 내용이 들어가있지 않고, 이러한 버전은 Git 에서 Branch 를 통해 관리하는 것이 좋다. 

마치며

여기까지 REST API 를 보다 'ful' 하게 작성하는 가이드를 적어보았다. REST 를 RESTful 로 만들기 위한 분명한 표준(Standard)은 없다. 그저 스타일이기 때문이다. 따라서 이를 제공하는 문서를 명확히 정리하는 것이 좋은 방법이다. RESTful API 를 만들 때 기억해야 할 것은 URI 를 사용하여 자원을 나타내고 XML, JSON 등의 포맷을 통해 표현하고 행위는 HTTP 메서드로 나타낸다는 점이다.

 

그저 GET, POST 두 가지만으로 Query Parameters 를 사용하여 PUT/PATCH, DELETE 를 표현하는 것은 상당히 안티패턴이므로 각별히 주의해야 한다. 언급한 것들 중, REST API 를 작성할 때 지켜주면 좋은 부분은 URI 규칙, HTTP 메서드, HTTP 상태코드이며 다른 것은 고사하고 이 부분은 만큼은 반드시 지켜주는 것이 좋을 것이다.

    '웹' 카테고리의 다른 글
    • 아임포트(Iamport)로 결제기능 구현하기 - 일반결제
    • 아임포트(Iamport)로 결제기능 구현하기 - 준비
    • 쿠키(Cookie)와 세션(Sessions)에 대해 알아보자
    • OAuth 2.0 클라이언트 만들기(feat. 깃허브)
    API, rest api, restful api, 가이드
    정상우
    정상우
    과거의 배움으로 현재를 바꾸고 미래를 만듭니다. #25+2살 #INFJ #개발자 #브런치작가
    댓글쓰기
    1. 지굥
      2022.01.24 16:14 신고
      안녕하세요, RESTful API가 어려웠는데 포스팅 덕분에 조금 이해할 수 있게 됐습니다. 읽다가 궁금한게 생겼는데 URI 규칙의 '동사는 HTTP로 대체한다'는 부분에서 user는 왜 복수가 아니고 단수로 표시되어 있나요? 오타인건지 아니면 제가 아직 이해하지 못한 다른 이유가 있는 건지 궁금해서 여쭤봅니다 ㅎㅎ
      수정/삭제댓글쓰기댓글보기
      1. 정상우
        2022.01.24 16:40 신고
        엇 그냥 제가 신경쓰지 않은 것이라고 생각하시면 됩니다. 단수로 표기한 부분이 무언가 특별해서 그런건 아니고 그냥 이 글을 썼을 때 복수형으로 쓰는걸 의식하지 않고 써서 그랬네용 ㅎㅎ 복수형이라고 생각해주세용
        수정/삭제
    다음 글
    hELLO 티스토리 스킨을 소개합니다.
    이전 글
    쿠키(Cookie)와 세션(Sessions)에 대해 알아보자
    • 이전
    • 1
    • ···
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • ···
    • 120
    • 다음

    티스토리툴바