콘텐츠 대표 이미지 - 개발자 인싸력 급상승! RESTful API 설계 완전 정복 가이드

개발자 인싸력 급상승! RESTful API 설계 완전 정복 가이드

서버와 클라이언트의 아름다운 대화를 위한 필수 교양, 지금 바로 시작합니다.

API? REST? 일단 용어부터 제대로 알고 가자!

개발의 세계에 막 발을 들였거나, 혹은 이미 현업에서 뛰고 있는 개발자라도 'API'라는 단어는 정말 지겹도록 듣게 될 거야.
"이번 프로젝트 API 명세 나왔어요?", "그 기능 API로 연동해야 해요." 등등...
마치 공기처럼 당연하게 쓰이지만, 막상 "API가 정확히 뭐야?"라고 물으면 말문이 턱 막히는 경험, 다들 한 번쯤 있지 않아?

걱정 마. 오늘 이 자리에서 그 애매모호함을 완전히 걷어내 줄게.
API는 Application Programming Interface의 약자야. 번역하면 '애플리케이션 프로그래밍 인터페이스'.
...음, 번역해도 어렵지? 😅

더 쉽게 가보자. API는 '소프트웨어 컴포넌트들이 서로 소통하는 방법'에 대한 약속이야.
레스토랑에 갔다고 상상해봐. 넌 손님이고, 주방에는 셰프가 있어. 네가 직접 주방에 뛰어 들어가서 "스테이크 미디엄 레어로 구워주시고, 사이드는 감자튀김, 소스는 홀그레인 머스타드요!"라고 외칠 순 없잖아? 대신 넌 '웨이터'에게 주문을 하지.

여기서 손님은 '클라이언트'(너의 앱, 웹사이트), 주방은 '서버'(데이터와 로직이 있는 곳), 그리고 웨이터가 바로 'API'인 거야.
웨이터는 정해진 메뉴판(API 명세서)을 통해 주문을 받고, 주방에 정확하게 전달한 뒤, 완성된 요리(데이터)를 너에게 가져다주지. 이 과정에서 넌 주방이 얼마나 복잡한지, 셰프가 어떤 도구를 쓰는지 전혀 알 필요가 없어. 그저 약속된 방식으로 주문하고 결과를 받으면 끝!

이처럼 API는 복잡한 내부 구현을 싹 감춰주고, 개발자들이 정해진 규칙에 따라 편하게 기능을 가져다 쓸 수 있게 해주는 마법 같은 존재야. 날씨 정보, 지도, 결제 시스템 등 우리가 사용하는 수많은 서비스가 바로 이 API 덕분에 가능한 거지.

그렇다면 REST는 또 뭐야?

API가 '소통 방식'이라는 큰 개념이라면, REST(Representational State Transfer)는 그 소통 방식을 어떻게 설계할지에 대한 하나의 '건축 스타일(Architectural Style)'이야. 수많은 API 디자인 방법론 중 현재 웹에서 가장 널리 쓰이는, 말 그대로 '대세' 스타일이지.

REST 스타일을 따라 만든 API를 우리는 'RESTful API'라고 불러. 마치 '힙스터' 스타일로 옷을 입은 사람을 '힙하다'고 부르는 것처럼 말이야. REST는 웹의 창시자 중 한 명인 로이 필딩(Roy Fielding)이 "웹은 이렇게 만들어져야 해!"라며 제시한 원칙들의 집합인데, 이게 웹의 장점을 너무나 잘 살려서 사실상 표준처럼 굳어진 거야.

오늘 우리는 바로 이 'RESTful'하게 API를 만드는 법에 대해 깊이 파고들 거야. 이걸 제대로 이해하고 나면, 너의 개발 실력은 물론, 다른 개발자와의 협업 능력까지 한 단계 레벨업할 수 있을 거라고 장담해!

RESTful API의 6가지 핵심 원칙 (이것만 알면 반은 먹고 들어간다)

로이 필딩은 REST 아키텍처를 만족시키기 위한 6가지 제한 조건(constraints)을 제시했어. 처음엔 좀 딱딱하게 들릴 수 있지만, 하나씩 뜯어보면 전부 합리적이고 당연한 이야기들이야. 이 원칙들을 지켜야 비로소 "나 RESTful API 좀 만들어봤다"고 말할 수 있는 거지.

1. 클라이언트-서버 (Client-Server) 구조

이건 앞에서 레스토랑 비유로 이미 설명했지? 클라이언트와 서버의 역할을 명확하게 분리하는 거야.

  • 클라이언트: 사용자 인터페이스(UI)에 집중. 예를 들어 웹 프론트엔드 개발자는 어떻게 하면 사용자에게 데이터를 예쁘고 편리하게 보여줄지에만 신경 쓰면 돼.
  • 서버: 비즈니스 로직, 데이터 저장, 처리에 집중. 백엔드 개발자는 데이터베이스를 어떻게 관리하고, 요청에 따라 어떤 데이터를 보내줄지에만 신경 쓰면 돼.

이렇게 둘의 관심사를 분리하면 뭐가 좋을까?
일단 독립적인 개발이 가능해져. 프론트엔드 팀과 백엔드 팀은 서로 약속된 API 명세만 보고 각자 개발을 진행할 수 있어. 서로의 작업이 끝날 때까지 기다릴 필요가 없는 거지.
또한 확장성이 좋아져. 서버는 그대로 둔 채, 클라이언트를 웹에서 모바일 앱(iOS, Android)으로 확장하거나, 그 반대도 훨씬 쉬워져. 서로에게 종속되지 않았기 때문이야.

프로그램개발 > 홈페이지/웹개발 개발자 인싸력 급상승! RESTful API 설계 완전 정복 💻 클라이언트 (Client) 사용자 인터페이스(UI) 제공 (웹 브라우저, 모바일 앱) ⚙️ 서버 (Server) 비즈니스 로직 및 데이터 처리 (API, 데이터베이스) API 요청 (Request) API 응답 (Response)

2. 무상태성 (Stateless)

이게 정말 정말 중요한 개념이야. Stateless, 즉 상태를 저장하지 않는다는 뜻.
서버는 클라이언트의 이전 요청이나 현재 상태 같은 어떤 컨텍스트도 저장하지 않아. 모든 요청은 그 자체로 완전한 정보를 담고 있어야 하고, 서버는 그냥 그 요청만 보고 딱 자기 할 일만 하고 응답하면 끝이야.

다시 레스토랑 비유를 들어볼까?

  • Stateful (상태 저장) 웨이터: "아까 스테이크 시키셨던 손님이죠? 디저트 뭐 드릴까요?" -> 손님의 이전 주문을 기억하고 있음.
  • Stateless (무상태) 웨이터: "주문 도와드리겠습니다." (스테이크 주문) ... 잠시 후 ... "주문 도와드리겠습니다." (디저트 주문) -> 매번 새로운 손님처럼 응대. 이전 주문을 기억할 의무가 없음.

Stateless 방식이 처음엔 비효율적으로 보일 수 있어. "아니, 매번 자기소개를 해야 한다고?" 싶겠지.
하지만 서버 입장에서 생각해보면 이건 엄청난 축복이야. 클라이언트의 상태를 일일이 기억하고 관리할 필요가 없으니 서버의 부담이 확 줄어들어. 어떤 클라이언트가 요청하든 상관없이 동일한 요청은 동일한 방식으로 처리하면 되니까.

이 덕분에 서버를 여러 대로 늘리는 '스케일 아웃(Scale-out)'이 매우 쉬워져. 롯데월드 매표소가 10개 있다고 상상해봐. 어떤 매표소에 가든 똑같이 "자유이용권 성인 1장이요"라고 말하면 표를 살 수 있지? 매표소 직원이 "아까 옆 창구에서 뭐 물어보셨죠?"라고 되묻지 않잖아. 이게 바로 Stateless의 힘이야. 어떤 서버로 요청이 분산되든 상관없이 일관된 처리가 가능한 거지.

잠깐! 그럼 로그인은 어떻게 해요?

"Stateless라면서 로그인은 어떻게 상태를 유지하나요?" 라는 질문이 나올 수 있어. 아주 좋은 질문이야!
로그인 정보 같은 '인증' 상태는 서버가 직접 세션에 저장하는 게 아니라, 클라이언트가 모든 요청에 자신의 신분증을 포함해서 보내는 방식으로 처리해. 대표적인 게 토큰(Token) 기반 인증이야.

로그인에 성공하면 서버는 클라이언트에게 '출입증' 역할을 하는 토큰(예: JWT)을 발급해줘. 클라이언트는 이후 API를 요청할 때마다 이 토큰을 요청 헤더에 담아서 보내는 거야. "나 이런 권한 있는 사람인데, 이 데이터 좀 줘!" 하고 말이지. 서버는 그 토큰이 유효한지만 검증하면 끝. 서버 자체에 "이 유저는 로그인 중"이라는 상태를 저장할 필요가 없는 거지. 책임과 상태를 클라이언트에게 넘기는 거야.

3. 캐시 가능 (Cacheable)

웹의 가장 큰 특징 중 하나는 '느리다'는 거야. 매번 서버까지 가서 데이터를 가져오는 건 비용이 비싸. 그래서 REST는 클라이언트나 중간 프록시 서버가 응답을 '캐싱(Caching)'할 수 있도록 허용해.

서버는 응답 데이터를 보내줄 때, 이 데이터가 캐시 가능한지 아닌지, 가능하다면 얼마나 오랫동안 유효한지를 명시해줘야 해. 이건 HTTP 헤더의 `Cache-Control`, `Expires` 같은 필드를 통해 이루어져.

예를 들어, 자주 바뀌지 않는 회사 소개 페이지나 상품 카테고리 목록 같은 데이터는 한 번 받아온 뒤 클라이언트(브라우저)에 저장해두고, 다음 요청 시에는 서버에 또 물어보지 않고 저장된 데이터를 바로 사용하는 거지. 이걸 '브라우저 캐시'라고 해.

이렇게 캐시를 잘 활용하면?

  • 성능 향상: 클라이언트가 느끼는 로딩 속도가 빨라짐.
  • 서버 부하 감소: 서버에 대한 요청 횟수 자체가 줄어듦.
  • 네트워크 트래픽 감소: 불필요한 데이터 전송이 줄어듦.

한마디로 모두에게 이득이야. 잘 만든 RESTful API는 캐시 전략까지 꼼꼼하게 고려해야 해.

4. 계층화 시스템 (Layered System)

이것도 재밌는 원칙이야. 클라이언트는 서버에 직접 연결되는지, 아니면 그 사이에 로드 밸런서, 프록시, 게이트웨이 같은 여러 계층(Layer)을 거치는지 알 수 없어야 한다는 거야.

클라이언트는 그냥 엔드포인트(API 주소)에 대고 요청을 던질 뿐, 그 뒤에서 어떤 복잡한 일들이 벌어지는지는 전혀 신경 쓸 필요가 없어.

이 구조는 시스템 전체의 유연성과 확장성을 높여줘. 예를 들어,

  • 로드 밸런서: 수많은 요청을 여러 서버에 골고루 분산시켜 안정성을 높일 수 있어.
  • 보안 계층: 암호화나 인증을 전담하는 서버를 앞에 둬서 보안을 강화할 수 있어.
  • 캐시 서버: 자주 사용되는 응답을 저장해두는 캐시 서버를 둬서 성능을 향상시킬 수 있어.

클라이언트는 이 모든 중간 계층의 존재를 모르기 때문에, 서버 개발자는 필요에 따라 자유롭게 시스템 아키텍처를 변경하고 확장할 수 있는 유연성을 얻게 돼.

RESTful API 계층화 시스템 (Layered System) 클라이언트 (Client) 로드 밸런서 (Load Balancer) 요청 분산 API 게이트웨이 (API Gateway) 인증 / 로깅 실제 서버 (Application Server) 비즈니스 로직 클라이언트는 이 중간 계층의 존재를 모릅니다.

5. 유니폼 인터페이스 (Uniform Interface)

이게 REST를 가장 REST답게 만드는, 어찌 보면 가장 중요한 원칙이야. '일관된 인터페이스'라는 뜻으로, 리소스에 접근하는 방식을 하나로 통일해서 아키텍처를 단순화하고, 상호작용을 명확하게 만드는 게 목표야. 이 원칙은 또 4개의 하위 원칙으로 나뉘어.

5-1. 리소스 식별 (Identification of resources)

모든 '자원(Resource)'은 고유한 ID, 즉 URI(Uniform Resource Identifier)를 통해 식별되어야 한다는 원칙이야. 여기서 자원은 '데이터' 그 자체를 의미해. 예를 들어 '사용자 정보', '게시글', '상품 목록' 같은 것들이 모두 자원이지.

RESTful API에서는 이 자원들을 URI로 표현해. 마치 모든 건물에 '주소'가 있는 것처럼 말이야.


https://api.example.com/users/123  (ID가 123인 사용자 정보)
https://api.example.com/posts/456   (ID가 456인 게시글 정보)
https://api.example.com/users/123/posts (ID가 123인 사용자가 쓴 게시글 목록)
  

중요한 건, URI는 '자원'을 가리키는 명사로 작성해야 한다는 점이야. `/getUsers`나 `/deletePost`처럼 행위를 나타내는 동사를 쓰면 안 돼. 행위는 뒤에 나올 HTTP 메서드가 담당할 거거든.

5-2. 표현을 통한 리소스 조작 (Manipulation of resources through representations)

클라이언트가 리소스의 URI를 알고 있다고 해서, 서버에 있는 데이터베이스 파일 자체를 직접 받는 건 아니야. 클라이언트는 리소스의 '표현(Representation)'을 받아서 조작해.

이 '표현'이라는 건 보통 JSON이나 XML 같은 특정 포맷의 데이터를 의미해. 클라이언트가 서버로부터 `users/123`이라는 리소스를 받았다면, 그건 아래와 같은 JSON 형태의 '표현'일 거야.


{
  "id": 123,
  "name": "홍길동",
  "email": "gildong@example.com"
}
  

클라이언트가 이 사용자의 이메일을 바꾸고 싶다면? 이 JSON 표현을 수정해서 서버에 다시 보내는 방식으로 리소스를 조작하는 거지. "ID가 123인 유저의 상태를 이 JSON 데이터처럼 바꿔줘!" 라고 요청하는 셈이야.

5-3. 자기 서술적 메시지 (Self-descriptive messages)

모든 메시지(요청과 응답)는 그 메시지를 어떻게 처리해야 할지에 대한 정보를 스스로 담고 있어야 한다는 원칙이야. 메시지만 보고도 이게 뭔지, 어떻게 해석해야 하는지 알 수 있어야 한다는 거지.

예를 들어, 요청 메시지는 어떤 HTTP 메서드(GET, POST, PUT 등)를 사용해야 하는지, 어떤 종류의 데이터를 보내는지(`Content-Type: application/json`) 등을 명시해야 해.

응답 메시지 역시 마찬가지야. 성공인지 실패인지 알려주는 HTTP 상태 코드(200 OK, 404 Not Found 등)를 포함하고, 본문에 담긴 데이터가 어떤 형식인지(`Content-Type: application/json`)를 알려줘야 해.

이렇게 되면 클라이언트와 서버는 메시지만 보고도 서로의 의도를 명확하게 파악할 수 있어서, 별도의 문서나 약속 없이도 상호작용이 가능해져.

5-4. HATEOAS (Hypermedia as the Engine of Application State)

이름부터 어렵지? '헤이티오스'라고 읽어. REST 원칙의 끝판왕이자, 가장 많은 개발자들이 제대로 지키지 않는 원칙이기도 해.

간단히 말하면, 응답 메시지에 관련된 다음 행동(액션)을 할 수 있는 링크를 포함시켜라는 거야.

우리가 웹서핑을 할 때를 생각해보자. 네이버 메인 페이지에 들어가면 '뉴스', '쇼핑', '블로그' 같은 링크들이 있지? 우리는 그 링크를 눌러서 다음 페이지로 자연스럽게 이동해. 모든 URL을 미리 외워두고 주소창에 직접 치지 않잖아.

HATEOAS는 API도 이와 같아야 한다는 사상이야. 예를 들어, `users/123` 정보를 요청했을 때, 단순히 사용자 정보만 주는 게 아니라 그 사용자와 관련된 다른 API를 호출할 수 있는 링크를 함께 제공하는 거지.


{
  "id": 123,
  "name": "홍길동",
  "email": "gildong@example.com",
  "links": [
    {
      "rel": "self",
      "href": "https://api.example.com/users/123",
      "method": "GET"
    },
    {
      "rel": "posts",
      "href": "https://api.example.com/users/123/posts",
      "method": "GET"
    },
    {
      "rel": "update",
      "href": "https://api.example.com/users/123",
      "method": "PUT"
    },
    {
      "rel": "delete",
      "href": "https://api.example.com/users/123",
      "method": "DELETE"
    }
  ]
}
  

이렇게 링크를 포함해주면 클라이언트는 응답만 보고도 "아, 이 유저의 게시글을 보려면 이 주소로 GET 요청을 보내면 되는구나", "수정하려면 같은 주소로 PUT 요청을 보내면 되는구나" 하고 다음 행동을 결정할 수 있어. API의 엔드포인트가 변경되더라도 클라이언트는 코드를 수정할 필요 없이 서버가 내려주는 링크를 따라가기만 하면 되니, 클라이언트와 서버 간의 의존성(결합도)이 크게 낮아지는 엄청난 장점이 있어.

HATEOAS, 꼭 지켜야 할까?

이론적으로는 HATEOAS가 가장 이상적인 REST의 모습이야. 하지만 현실적으로는 구현이 복잡하고, 클라이언트와 서버가 미리 API 명세를 공유하는 경우가 대부분이라 그 이점이 크게 와닿지 않을 수 있어. 그래서 많은 상용 API들이 HATEOAS를 완전히 구현하지는 않아.

하지만 그 개념을 이해하고, 최소한 `self` 링크(자기 자신을 가리키는 링크) 정도라도 포함하려는 노력은 API의 완성도를 높이는 데 큰 도움이 될 거야.

6. 코드 온 디맨드 (Code-on-Demand) - 선택 사항

이건 유일한 선택적 원칙이야. 서버가 클라이언트에게 실행 가능한 코드(예: JavaScript)를 보내주면, 클라이언트가 그 코드를 실행할 수 있다는 개념이야. 우리가 웹사이트에 접속하면 브라우저가 HTML, CSS와 함께 JavaScript를 다운로드해서 실행하는 걸 생각하면 돼.

이 원칙 덕분에 클라이언트는 미리 모든 기능을 구현해놓지 않아도, 필요할 때 서버로부터 기능을 다운로드해서 확장할 수 있어. 하지만 모든 클라이언트가 이 코드를 이해하고 실행할 수 있다는 보장이 없어서, 일반적으로는 잘 사용되지 않는 원칙이야.

실전! RESTful API 설계 모범 사례 (이렇게만 하면 평타 이상)

자, 이제 이론 공부는 끝났어. 지금부터는 실제 필드에서 API를 어떻게 '잘' 설계하는지에 대한 현실적인 꿀팁들을 대방출할 시간이야. 이 원칙들만 잘 지켜도 "저 사람 API 설계 좀 할 줄 아네?" 소리 들을 수 있을 거야.

1. URI 설계 원칙: 주소는 명확하고 일관되게!

API의 첫인상은 바로 URI야. 잘 지은 URI는 그 자체로 훌륭한 문서가 돼.

  • 동사 대신 명사를 사용하라: 이건 위에서도 강조했지? URI는 리소스를 표현하는 곳이야. 행위는 HTTP 메서드에게 맡겨.
    Bad: `/getUsers`, `/createUser`, `/deleteUser/1`
    Good: `/users` (GET), `/users` (POST), `/users/1` (DELETE)

  • 리소스는 복수형 명사를 사용하라: 개별 리소스는 컬렉션의 일부라는 것을 명확히 하기 위해 복수형을 쓰는 게 일반적이야.
    Bad: `/user/1`, `/post/123`
    Good: `/users/1`, `/posts/123`

  • 슬래시(/)는 계층 관계를 나타내는 데 사용하라: 리소스 간의 관계를 표현할 때 유용해.
    Example: `/users/1/posts` (1번 유저의 모든 게시글)
    Example: `/users/1/posts/5` (1번 유저의 5번 게시글)

  • URI에는 소문자만 사용하라: 대소문자를 구분하는 서버가 있기 때문에, 혼동을 피하기 위해 모두 소문자로 통일하는 게 좋아. RFC 3986(URI 표준)은 대소문자를 구별하도록 규정하고 있어.
    Bad: `/Users/1/Posts`
    Good: `/users/1/posts`

  • 단어 사이의 구분은 하이픈(-)을 사용하라: 언더스코어(_)는 가독성이 떨어지거나 일부 폰트에서 잘 보이지 않을 수 있어. 하이픈(-)이 더 널리 쓰이고 명확해.
    Bad: `/user_groups`
    Good: `/user-groups`

  • 파일 확장자는 포함하지 마라: URI는 리소스의 '표현'이 아닌 '자원' 자체를 가리켜야 해. 데이터 포맷(JSON, XML 등)은 요청 헤더의 `Accept` 필드를 통해 클라이언트가 지정하는 게 맞아.
    Bad: `/users/1.json`
    Good: `/users/1` (그리고 요청 헤더에 `Accept: application/json` 포함)

2. HTTP 메서드: 적재적소에 동사를 사용하라!

URI가 명사라면, HTTP 메서드는 동사야. 리소스에 어떤 행위를 할 것인지를 명확하게 표현해야 해. CRUD(Create, Read, Update, Delete) 연산과 HTTP 메서드를 짝지어보자.

RESTful API: HTTP 메서드와 CRUD 매핑 HTTP 메서드 CRUD 대상 리소스 설명 멱등성 / 안전성 GET Read (조회) /posts, /posts/1 리소스를 조회 O / O POST Create (생성) /posts 새 리소스를 생성 X / X PUT Update (전체 교체) /posts/1 리소스를 통째로 교체 O / X PATCH Update (부분 수정) /posts/1 리소스의 일부만 수정 X / X DELETE Delete (삭제) /posts/1 리소스를 삭제 O / X (멱등성 / 안전성)

멱등성(Idempotent)과 안전성(Safe)이 뭔가요?

  • 안전(Safe): 이 메서드를 호출해도 리소스가 변경되지 않음을 의미해. 즉, 읽기 전용 작업이야. GET, HEAD, OPTIONS가 여기에 해당돼. 아무리 많이 호출해도 서버의 데이터는 바뀌지 않아.

  • 멱등성(Idempotent): 이 메서드를 한 번 호출하든, 여러 번 호출하든 결과가 똑같다는 의미야.
    • GET: 1번 게시글을 한 번 조회하든 열 번 조회하든 항상 같은 1번 게시글이 보여. (멱등성 O)
    • PUT: 1번 게시글의 제목을 "안녕"으로 바꾸는 요청을 한 번 보내든 열 번 보내든, 최종 결과는 "제목이 '안녕'인 1번 게시글"로 동일해. (멱등성 O)
    • DELETE: 1번 게시글을 삭제하는 요청을 한 번 보내면 삭제되고, 두 번째 보내면 "이미 없음" 에러가 뜨겠지만 어쨌든 '1번 게시글이 없는 상태'는 동일하게 유지돼. (멱등성 O)
    • POST: `/posts`에 게시글 생성 요청을 보낼 때마다 새로운 게시글이 계속 생성돼. 결과가 계속 바뀌지. (멱등성 X)

이 개념을 이해하면 왜 특정 작업에 특정 메서드를 써야 하는지 더 명확하게 알 수 있어. 특히 네트워크 오류로 같은 요청을 재시도해야 할 때, 멱등성이 보장되는 메서드는 안심하고 다시 보낼 수 있지.

PUT vs PATCH, 차이점을 명확히 알자!

둘 다 '수정'이라 헷갈릴 수 있는데, 아주 중요한 차이가 있어.

  • PUT: 리소스 전체 교체. 요청에 포함되지 않은 필드는 `null`이나 초기값으로 변경될 수 있어.
    예를 들어, `{ "name": "김철수", "age": 30 }`인 사용자에게 `PUT /users/1` 요청으로 `{ "age": 31 }`만 보내면, 이름(name) 필드는 사라지고 나이만 31로 업데이트될 수 있어.

  • PATCH: 리소스 부분 수정. 요청에 포함된 필드만 변경하고 나머지는 그대로 둬.
    위와 같은 상황에서 `PATCH /users/1` 요청으로 `{ "age": 31 }`을 보내면, 이름은 "김철수" 그대로 유지되고 나이만 31로 바뀌어.

따라서 간단한 필드 몇 개만 수정할 때는 PATCH가 훨씬 효율적이고 안전해.

3. 응답 설계: 친절하고 일관된 피드백 주기

API는 결국 '대화'야. 클라이언트의 요청에 대해 서버가 얼마나 친절하고 명확하게 대답해주느냐가 API의 품질을 결정해.

3-1. 정확한 HTTP 상태 코드 사용하기

"성공하면 200, 실패하면 500" 이렇게만 쓰면 안 돼! 상황에 맞는 디테일한 상태 코드를 사용하면 클라이언트는 응답 본문을 열어보지 않고도 결과를 예측할 수 있어.

  • 2xx (성공):
    • 200 OK: 요청이 성공적으로 처리됨. (GET, PUT, PATCH, DELETE에 대한 일반적인 성공 응답)
    • 201 Created: 리소스가 성공적으로 생성됨. (POST 요청에 대한 성공 응답) 응답 헤더의 `Location` 필드에 생성된 리소스의 URI를 포함해주는 게 베스트 프랙티스.
    • 204 No Content: 요청은 성공했지만 반환할 콘텐츠가 없음. (예: DELETE 요청 성공 후)

  • 4xx (클라이언트 오류): 이건 "네가 잘못했어!" 라는 뜻이야.
    • 400 Bad Request: 요청 구문이 잘못됨. (예: 필수 파라미터 누락, 데이터 형식 오류)
    • 401 Unauthorized: 인증되지 않은 사용자. (로그인이 필요함)
    • 403 Forbidden: 인증은 됐지만 접근 권한이 없음. (로그인은 했지만 어드민 전용 페이지에 접근 시도)
    • 404 Not Found: 요청한 리소스가 존재하지 않음.
    • 409 Conflict: 리소스의 현재 상태와 충돌하여 요청을 처리할 수 없음. (예: 중복된 아이디로 회원가입 시도)

  • 5xx (서버 오류): 이건 "미안, 내 잘못이야..." 라는 뜻이야.
    • 500 Internal Server Error: 서버 내부에서 예상치 못한 오류 발생. 가장 일반적인 서버 에러.
    • 503 Service Unavailable: 서버가 과부하 또는 점검 중으로 현재 요청을 처리할 수 없음.

3-2. 일관된 응답 본문 구조 유지하기

성공했을 때와 실패했을 때의 응답 구조를 일관되게 유지하는 게 중요해. 그래야 클라이언트가 파싱하기 편하거든.

성공 응답 예시 (JSend 스타일):


// 단일 리소스
{
  "status": "success",
  "data": {
    "post": {
      "id": 1,
      "title": "RESTful API 완전 정복",
      "content": "..."
    }
  }
}

// 리소스 목록
{
  "status": "success",
  "data": {
    "posts": [
      { "id": 1, "title": "..." },
      { "id": 2, "title": "..." }
    ]
  }
}
  

실패(에러) 응답 예시:


{
  "status": "fail",
  "data": {
    "title": "필수 입력 항목입니다."
  }
}

// 혹은 더 자세하게
{
  "status": "error",
  "message": "입력 값이 유효하지 않습니다.",
  "code": "INVALID_INPUT",
  "errors": [
    {
      "field": "email",
      "message": "올바른 이메일 형식이 아닙니다."
    }
  ]
}
  

이렇게 응답을 감싸주는 걸 Envelope(봉투) 방식이라고 해. 메타데이터(상태, 페이징 정보 등)와 실제 데이터를 분리할 수 있어서 확장성이 좋아져.

3-3. 페이징, 필터링, 정렬, 검색 기능 제공하기

리소스 목록을 반환할 때, 수천 개의 데이터를 한꺼번에 보내면 네트워크와 클라이언트 모두에게 재앙이야. 똑똑한 API는 클라이언트가 원하는 데이터만 골라 가져갈 수 있도록 도와줘야 해. 이런 기능들은 보통 쿼리 파라미터(Query Parameter)를 통해 구현해.

  • 페이징 (Pagination): 데이터를 페이지 단위로 잘라서 제공.
    `GET /posts?page=2&limit=20` (2페이지의 게시글을 20개씩 가져오기)

  • 필터링 (Filtering): 특정 조건에 맞는 데이터만 필터링.
    `GET /posts?status=published&author_id=1` (상태가 'published'이고 작성자 ID가 1인 게시글만)

  • 정렬 (Sorting): 결과를 특정 기준으로 정렬.
    `GET /posts?sort=-createdAt` (생성일 기준 내림차순 정렬. `-`를 붙여서 내림차순 표현)

  • 검색 (Searching): 특정 키워드로 검색.
    `GET /posts?q=API` ('API'라는 단어가 포함된 게시글 검색)

  • 필드 선택 (Fields): 필요한 필드만 선택적으로 가져오기.
    `GET /posts?fields=id,title,author` (id, title, author 필드만 응답에 포함)

이런 기능들을 잘 구현해두면 API의 효율성과 사용성이 극대화돼.

고급 스킬: API 버전 관리, 인증, 그리고 문서화

기본기를 다졌다면, 이제 API를 더 견고하고 전문적으로 만들어 줄 고급 기술들을 알아볼 차례야.

1. API 버전 관리 (Versioning)

서비스는 계속 발전하고, API도 바뀌기 마련이야. 그런데 API를 확 바꿔버리면 기존에 잘 쓰고 있던 클라이언트 앱들이 갑자기 먹통이 되는 대참사가 발생하겠지? 이걸 'Breaking Change'라고 해.

이런 재앙을 막기 위해 우리는 버전 관리를 해야 해. 기존 버전은 그대로 유지하면서, 새로운 버전의 API를 따로 만드는 거야. 클라이언트는 자신이 원하는 버전의 API를 선택해서 호출할 수 있어.

버전을 명시하는 방법은 크게 3가지가 있어.

  1. URI에 버전 정보 넣기 (가장 흔하고 직관적):
    `https://api.example.com/v1/posts`
    `https://api.example.com/v2/posts`
    장점: 브라우저에서 바로 확인 가능하고, 직관적이라 이해하기 쉬워. 가장 널리 쓰이는 방식.
    단점: 버전이 올라갈 때마다 URI가 바뀌어서 '하나의 리소스는 하나의 URI를 가져야 한다'는 원칙에 약간 어긋나 보일 수 있음.

  2. 쿼리 파라미터에 버전 정보 넣기:
    `https://api.example.com/posts?version=1`
    장점: URI가 바뀌지 않음.
    단점: 코드가 지저분해질 수 있고, 버전별로 라우팅하기가 URI 방식보다 까다로울 수 있음.

  3. 요청 헤더에 버전 정보 넣기 (가장 RESTful한 방식):
    `Accept: application/vnd.example.v1+json`
    장점: URI를 더럽히지 않으면서 HTTP 헤더를 통해 정보를 전달하므로 가장 REST 원칙에 부합.
    단점: 일반적인 브라우저에서는 테스트하기 어렵고, 사용하기가 번거로움.

어떤 방식이 절대적으로 옳다고 할 순 없지만, 대부분의 경우 URI에 버전을 명시하는 방식(`v1`, `v2` 등)이 가장 실용적이고 널리 받아들여지고 있어.

2. 인증과 인가 (Authentication & Authorization)

아무나 우리 API를 막 쓰게 할 순 없지. 사용자를 식별하고 권한을 제어하는 건 필수야.

  • 인증 (Authentication): "당신은 누구십니까?" -> 사용자의 신원을 확인하는 과정. (예: 로그인)
  • 인가 (Authorization): "당신이 이 작업을 할 수 있습니까?" -> 인증된 사용자가 특정 리소스에 접근할 권한이 있는지 확인하는 과정. (예: 일반 유저가 관리자 페이지 접근 시도)

현대적인 API에서는 주로 토큰(Token) 기반 인증을 사용해. 그중에서도 JWT(JSON Web Token)가 대세야.

클라이언트가 로그인에 성공하면 서버는 암호화된 사용자 정보가 담긴 JWT를 발급하고, 클라이언트는 이후 모든 요청의 `Authorization` 헤더에 이 토큰을 실어 보내. `Authorization: Bearer ` 이런 식으로 말이야. 서버는 이 토큰을 검증해서 사용자를 식별하고 권한을 체크해. Stateless 원칙을 지키면서도 안전하게 인증/인가를 구현할 수 있는 좋은 방법이지.

3. API 문서화 (Documentation)

문서화되지 않은 API는 존재하지 않는 API나 마찬가지다.

네가 아무리 완벽한 API를 만들어도, 사용법을 알려주지 않으면 아무도 쓸 수 없어. API 문서는 개발자를 위한 '사용자 매뉴얼'이야. 어떤 엔드포인트가 있는지, 각 엔드포인트는 어떤 파라미터를 받는지, 어떤 값을 반환하는지, 에러 코드는 뭐가 있는지 등을 상세하고 친절하게 설명해야 해.

예전처럼 워드 파일이나 위키에 수동으로 문서를 작성하는 시대는 지났어. 이제는 OpenAPI Specification (구 Swagger) 같은 도구를 사용하는 게 표준이야.

OpenAPI (Swagger)를 사용해야 하는 이유

OpenAPI는 API 명세를 표준화된 형식(YAML 또는 JSON)으로 작성하는 규격이야. 이걸 사용하면 다음과 같은 엄청난 이점들이 있어.

  • 자동화된 대화형 문서: 명세 파일만 있으면 API를 직접 테스트해볼 수 있는 멋진 UI의 문서를 자동으로 생성해줘.
  • 코드 생성: 명세를 기반으로 다양한 언어의 클라이언트 SDK 코드를 자동으로 생성할 수 있어.
  • 일관성 유지: 코드와 문서가 따로 놀 가능성을 줄여줘. 코드를 수정하면 명세도 같이 수정하는 문화를 만들 수 있지.

API를 설계하는 단계부터 OpenAPI 명세를 작성하는 습관을 들이는 게 좋아. 이건 너와 협업하는 다른 개발자들(프론트엔드, 모바일 등)을 위한 최고의 배려야.

물론 이런 전문적인 API 설계나 문서화 작업이 어렵게 느껴질 수도 있어. 그럴 땐 혼자 끙끙 앓기보다 전문가의 도움을 받는 것도 좋은 방법이야. 예를 들어 **재능넷** 같은 플랫폼에서는 실력 있는 백엔드 개발자들이 API 설계부터 구축, 문서화까지 도와주는 서비스를 제공하기도 하거든. 협업을 통해 배우고 성장하는 것도 개발자의 중요한 역량 중 하나지.

마무리하며: 잘 만든 API 하나, 열 서비스 안 부럽다

와, 정말 긴 여정이었어. 여기까지 스크롤을 내린 너 자신에게 박수를 보내줘! 👏
RESTful API 설계는 단순히 기술적인 규칙의 나열이 아니야. 이건 '어떻게 하면 더 명확하고, 일관되고, 예측 가능하게 소통할 수 있을까?'에 대한 깊은 고민의 결과물이야.

오늘 우리가 함께 살펴본 원칙과 모범 사례들을 다시 한번 정리해볼까?

  • REST의 6가지 원칙: 클라이언트-서버, 무상태성, 캐시, 계층화, 유니폼 인터페이스, 코드 온 디맨드. 이 철학을 이해하는 게 모든 것의 시작이야.
  • URI 설계: 리소스를 중심으로, 명사형, 복수형, 소문자, 하이픈 사용!
  • HTTP 메서드: GET, POST, PUT, PATCH, DELETE를 역할에 맞게 정확히 사용하기.
  • 응답 처리: 정확한 상태 코드와 일관된 응답 구조로 친절하게 피드백 주기.
  • 고급 스킬: 버전 관리로 하위 호환성을 보장하고, OpenAPI로 완벽한 문서를 제공하기.

기억해. API는 '개발자를 위한 UI'야. 사용자가 쓰기 편한 UI를 만들듯, 개발자가 쓰기 편한 API를 만드는 것을 목표로 삼아야 해. 잘 설계된 API는 협업의 효율을 극대화하고, 서비스의 확장성을 보장하며, 결국엔 더 나은 제품을 만드는 튼튼한 기반이 되어줄 거야.

물론 처음부터 완벽한 API를 설계하기는 어려울 수 있어. 하지만 오늘 배운 내용들을 나침반 삼아 계속 고민하고, 동료들과 토론하고, 더 나은 방법을 찾아나간다면, 어느새 "API 장인" 소리를 듣는 너 자신을 발견하게 될 거야. 혹시나 프로젝트를 진행하다가 API 설계에 대한 전문가의 조언이나 도움이 필요하다면, **재능넷**과 같은 전문가 플랫폼을 통해 해답을 찾아보는 것도 좋은 선택지가 될 수 있겠지.

자, 이제 이론은 충분해. 너의 다음 프로젝트에서 오늘 배운 것들을 마음껏 펼쳐보길 바랄게. Happy Coding!

댓글 작성

이 글에 대한 여러분의 생각을 들려주세요

댓글 0
댓글을 불러오는 중입니다.

아직 댓글이 없습니다.