REST API 쉽게 이해하기: 기본 개념부터 좋은 설계까지
API는 프로그램들이 서로 대화하는 데 사용되는 언어 또는 규칙이다. 그중에서도 REST API는 오늘날 웹에서 가장 널리 쓰이는 대화 방식이다. REST는 완전히 새로운 기술이 아니라, 웹을 성공으로 이끈 기존의 HTTP 통신 규칙을 잘 활용하여 만든 설계 원칙 모음이다.
이 글은 REST API가 무엇인지, 왜 중요한지, 그리고 어떻게 설계해야 하는지 초보자의 눈높이에서 차근차근 설명한다.
1. REST API, 왜 필요할까?
식당에 가서 음식을 주문하는 과정을 생각해보자. 우리는 메뉴판을 보고, 웨이터에게 원하는 음식을 말한다. 그러면 웨이터는 주문을 주방에 전달하고, 완성된 음식을 우리에게 가져다준다.
여기서 손님은 클라이언트(Client), 웨이터는 API, 주방은 서버(Server)에 비유할 수 있다.
- 클라이언트(손님): “파스타 주세요”처럼 필요한 것을 요청한다. (예: 웹 브라우저, 스마트폰 앱)
- 서버(주방): 요청을 받아 실제 작업을 처리한다. (예: 데이터 저장, 계산)
- API(웨이터): 클라이언트의 요청을 서버에 전달하고, 서버의 처리 결과를 다시 클라이언트에 돌려주는 중간 다리 역할을 한다.
REST API는 이 ‘웨이터’의 역할, 즉 소통 방식을 체계적으로 정리한 규칙이다. 이 규칙을 잘 따르면 클라이언트와 서버가 서로 다른 종류의 기기나 프로그램이라도 원활하게 대화할 수 있다. 예를 들어, 아이폰 앱(클라이언트)과 리눅스 서버가 REST API라는 공통 언어로 데이터를 주고받는 것이다.
2. REST를 REST답게 만드는 6가지 원칙
REST API를 제대로 설계하려면 6가지 원칙을 따라야 한다. 이 원칙들은 단순히 지켜야 할 규칙이 아니라, 시스템을 더 유연하고 확장 가능하게 만드는 중요한 장치이다.
2.1 클라이언트-서버 구조 (Client-Server)
손님이 주방에 직접 들어가 요리하지 않는 것처럼, 클라이언트와 서버의 역할은 명확히 분리되어야 한다.
- 클라이언트: 사용자 인터페이스(화면)에 집중한다.
- 서버: 데이터 저장 및 처리에 집중한다.
이렇게 역할을 나누면 서로에게 영향을 주지 않고 각자 독립적으로 개발하고 개선할 수 있다. 예를 들어, 서버는 그대로 둔 채 안드로이드 앱을 아이폰 앱으로 바꾸거나, 웹 디자인을 완전히 새롭게 개편하는 것이 가능해진다.
2.2 무상태 (Stateless)
웨이터가 손님의 이전 주문 내용을 기억하지 않는다고 생각해보자. 손님은 주문할 때마다 “아까 시켰던 거 말고요, 이번엔 피자 주세요”가 아니라 “피자 한 판 주세요”라고 매번 완전한 정보를 담아 요청해야 한다.
이처럼 REST API에서는 서버가 클라이언트의 이전 상태를 저장하거나 기억하지 않는다. 모든 요청은 그 자체로 완전한 정보를 담고 있어야 한다. 이렇게 하면 서버는 요청을 받을 때마다 이전 상황을 신경 쓸 필요가 없어 구조가 단순해지고, 어떤 서버가 요청을 처리하든 동일한 결과를 보장할 수 있어 확장성이 높아진다.
2.3 캐시 가능 (Cacheable)
손님이 메뉴판을 자리에 계속 두고 보는 것처럼, 클라이언트는 서버로부터 받은 데이터를 ‘캐시’ 즉, 저장해두고 재사용할 수 있다.
서버는 응답 데이터에 “이 데이터는 10분 동안 유효합니다”와 같은 캐시 정책을 명시할 수 있다. 클라이언트는 이 시간 동안 서버에 다시 물어보지 않고 저장해 둔 데이터를 사용한다. 이를 통해 불필요한 통신을 줄여 서버의 부담을 덜고, 사용자에게 더 빠른 응답을 제공할 수 있다. 예를 들어, 서버가 응답에 Cache-Control: public, max-age=3600 이라는 헤더를 포함하면, “이 데이터는 누구든 1시간(3600초) 동안 저장해두고 재사용해도 좋다”는 의미가 된다.
2.4 계층화 시스템 (Layered System)
손님은 웨이터에게 주문하지만, 그 뒤에 주방장이 요리하고, 다른 직원이 재료를 준비하는 등의 계층이 있다는 사실을 알 필요가 없다.
클라이언트와 서버 사이에도 로드 밸런서(교통 정리), 보안 시스템, 캐시 서버 등 다양한 계층을 둘 수 있다. 클라이언트는 오직 최종 서버와 통신한다고 생각하지만, 실제로는 보이지 않는 여러 계층들이 중간에서 다양한 역할을 수행한다. 이러한 구조는 시스템의 유연성과 확장성을 높여준다.
조금 더 구체적인 예시는 다음과 같다.
- 로드 밸런서(Load Balancer): 식당 입구에서 손님들을 여러 웨이터에게 나누어 보내주는 ‘안내 직원’과 같다. 한 웨이터에게 손님이 몰리지 않게 조절하여 서버의 부담을 나눈다.
- API 게이트웨이(API Gateway): 모든 주문(요청)을 가장 먼저 받는 ‘총괄 매니저’와 같다. 인증, 권한 확인 등 공통적인 업무를 처리하여 각 서버가 자신의 핵심 기능에만 집중할 수 있게 한다
- 캐시 서버(Cache Server): 자주 나가는 음료수처럼, 자주 요청되는 데이터를 미리 준비해두는 ‘작은 냉장고’와 같다. 주방(메인 서버)까지 가지 않아도 되니 더 빠른 응답이 가능하다.
2.5 균일한 인터페이스 (Uniform Interface)
REST의 가장 핵심적인 원칙으로, 정해진 하나의 통일된 방식으로 소통해야 한다는 의미다. 이는 다시 4개의 작은 규칙으로 나뉜다.
- 리소스 식별: 모든 정보는 ‘리소스’로 취급되며, 각 리소스는
https://my-blog.com/posts/123과 같은 고유한 주소(URI)를 가져야 한다. - 표현을 통한 리소스 조작: 클라이언트는 리소스의 상태를 ‘표현(Representation)’을 통해 전달받는다. 예를 들어, 서버에서 사용자 정보를 직접 가져오는 것이 아니라, 사용자 정보가 담긴 JSON 형식의 ‘문서’를 받는 것이다.
- 자기 서술적 메시지: 모든 요청은 그 자체로 무엇을 의미하는지 충분한 정보를 포함해야 한다. “이 메시지는 JSON 형식입니다” 또는 “이 요청은 데이터를 생성하기 위한 것입니다”와 같은 정보가 메시지 안에 담겨 있어, 누가 받더라도 메시지를 이해하고 처리할 수 있어야 한다.
- HATEOAS: 응답에 현재 리소스와 관련된 다음 행동(링크)들을 함께 포함시켜야 한다. 예를 들어, 123번 게시글 정보를 응답할 때, 그 게시글을 수정하거나 삭제할 수 있는 링크를 함께 제공하는 것이다.
{
"postId": 123,
"title": "나의 첫 게시글",
"content": "안녕하세요!",
"_links": {
"self": { "href": "/posts/123" },
"update": { "href": "/posts/123", "method": "PUT" },
"delete": { "href": "/posts/123", "method": "DELETE" }
}
}
클라이언트는 이 _links 정보만 보고 다음 동작을 결정할 수 있으므로, 나중에 서버 개발자가 게시글 수정 URI를 /posts/1/update 와 같이 바꾸더라도 클라이언트 코드를 수정할 필요가 없어진다. 이처럼 API의 변화에 더 유연하게 대처할 수 있게 되는 것이 HATEOAS의 가장 큰 장점이다.
2.6 코드 온 디맨드 (Code on Demand, 선택 사항)
이는 선택적인 원칙으로, 서버가 클라이언트에게 실행 가능한 코드(예: 자바스크립트)를 보내 클라이언트의 기능을 일시적으로 확장할 수 있게 하는 방식이다. 흔히 사용되는 방식은 아니므로 가볍게 이해하고 넘어가도 좋다.
3. REST API의 핵심 구성 요소
REST API는 크게 3가지 요소로 구성된다.
3.1 리소스와 URI
- 리소스(Resource): API가 다루는 모든 정보. (예: 사용자, 게시글, 상품)
- URI(Uniform Resource Identifier): 리소스의 고유한 주소. (예:
/users/1,/posts/45)
URI는 리소스를 가리키는 ‘명사’로, 그 주소만 봐도 무엇을 의미하는지 명확히 알 수 있도록 설계해야 한다.
3.2 HTTP 메서드
리소스에 어떤 ‘행위(동사)’를 할 것인지를 나타낸다.
| 메서드 | 목적 | 멱등성 |
|---|---|---|
| GET | 리소스 조회 | O |
| POST | 리소스 생성 | X |
| PUT | 리소스 전체 교체 | O |
| PATCH | 리소스 일부 수정 | X |
| DELETE | 리소스 삭제 | O |
멱등성(Idempotency)이란, 같은 요청을 여러 번 보내도 결과가 똑같은 성질을 의미한다. 네트워크 문제로 요청이 중복 전송되더라도, 멱등성이 보장되는 GET, PUT, DELETE 같은 메서드는 안심하고 재시도할 수 있다. POST는 호출할 때마다 새로운 리소스가 생성될 수 있으므로 멱등성이 보장되지 않는다.
3.3 표현과 상태 코드
-
표현(Representation): 리소스를 어떤 형태로 주고받을지 정하는 것. 주로 JSON 형식이 많이 사용된다. 서버는
Content-Type헤더를 통해 “이 데이터는 JSON 형식입니다”라고 알려준다. -
상태 코드(Status Code): 요청의 처리 결과를 나타내는 세 자리 숫자.
| 범위 | 의미 | 예시 |
|---|---|---|
| 2xx | 성공 | 200 OK: 요청이 성공적으로 처리됨. |
| 4xx | 클라이언트 오류 | 404 Not Found: 요청한 리소스를 찾을 수 없음. |
| 5xx | 서버 오류 | 500 Internal Server Error: 서버 내부에 문제가 발생함. |
상태 코드는 API의 ‘표정’과 같다. 200번대 코드는 웃는 얼굴, 400번대 코드는 “네가 뭔가 잘못 요청했어”라는 표정, 500번대 코드는 “미안, 내가 아파”라는 표정으로 이해하면 쉽다.
4. 좋은 REST API 설계를 위한 실천 방법
4.1 URI는 명사, 행위는 HTTP 메서드로
URI에는 동사를 사용하지 않는다. 리소스(명사)를 중심으로 설계하고, 행위는 HTTP 메서드에 맡긴다.
- 좋은 예:
GET /posts/1(1번 게시글을 조회한다) - 나쁜 예:
GET /getPostById?id=1
4.2 일관된 URI 규칙 지키기
URI는 소문자를 사용하고, 단어 사이는 하이픈(-)으로 연결하는 것이 좋다. 파일 확장자(예: .html)는 포함하지 않는다.
- 좋은 예:
/my-blog-posts - 나쁜 예:
/MyBlogPosts.jsp
4.3 API 버전 관리하기
API는 시간이 지나면서 변경될 수 있다. 기존 사용자에게 영향을 주지 않고 API를 업데이트하려면 버전 관리가 필요하다. URI에 버전을 명시하는 것이 가장 직관적이다.
- 예:
/v1/posts,/v2/posts
4.4 오류 응답은 친절하고 일관되게
요청이 실패했을 때, 단순히 “실패”라고만 알려주기보다 왜 실패했는지 구체적인 정보를 담아 일관된 형식으로 응답하는 것이 좋다.
{
"errorCode": "INVALID_INPUT",
"message": "입력값이 올바르지 않습니다.",
"details": "이메일 형식이 잘못되었습니다."
}
이렇게 하면 클라이언트 개발자가 오류의 원인을 쉽게 파악하고 대응할 수 있다.
4.5 목록 결과에 대한 규칙 정하기 (필터링, 정렬, 페이징)
많은 수의 데이터를 한 번에 모두 보여주는 것은 비효율적이다. 따라서 다음과 같이 결과를 제어할 수 있는 기능을 쿼리 파라미터(URI에서 ? 뒤에 오는 값)로 제공하는 것이 좋다.
- 필터링(Filtering):
GET /posts?status=published(특정 조건에 맞는 데이터만 조회. 예: ‘발행된’ 상태의 게시글만) - 정렬(Sorting):
GET /posts?sort=-created_at(특정 기준으로 결과를 정렬. 예: 생성일 순으로 정렬하되,-를 붙여 내림차순(최신순)으로) - 페이징(Paging):
GET /posts?page=2&limit=10(결과를 여러 페이지로 나누어 보여주기. 예: 한 페이지에 10개씩, 그중 2번째 페이지를 요청)
5. 간단한 REST API 예시: 블로그 API
블로그 게시글(Post)을 관리하는 간단한 API를 예시로 살펴보자.
1. 모든 게시글 조회하기
- 요청:
GET /posts - 응답 (200 OK):
[ { "id": 1, "title": "첫 번째 글" }, { "id": 2, "title": "두 번째 글" } ]
2. 특정 게시글 조회하기
- 요청:
GET /posts/1 - 응답 (200 OK):
{ "id": 1, "title": "첫 번째 글", "content": "이것은 첫 번째 게시글의 내용입니다." }
3. 새 게시글 작성하기
- 요청:
POST /posts - 요청 본문(Body):
{ "title": "새로운 글", "content": "새 글의 내용을 여기에 적습니다." } - 응답 (201 Created):
{ "id": 3, "title": "새로운 글", "content": "새 글의 내용을 여기에 적습니다." }
4. 게시글 수정하기
- 요청:
PUT /posts/3 - 요청 본문(Body):
{ "title": "수정된 새 글", "content": "내용이 완전히 수정되었습니다." } - 응답 (200 OK):
{ "id": 3, "title": "수정된 새 글", "content": "내용이 완전히 수정되었습니다." }
5. 게시글 삭제하기
- 요청:
DELETE /posts/3 - 응답 (204 No Content): 성공적으로 삭제되었으며, 별도의 응답 본문은 없음.
6. 결론: REST는 약속이다
REST API는 복잡한 기술이 아니라, 웹에서 효율적으로 소통하기 위한 ‘약속’ 또는 ‘설계 가이드’이다. 위에서 설명한 원칙과 규칙들을 잘 따르면, 누구나 이해하기 쉽고 사용하기 편리하며, 변화에 유연하게 대처할 수 있는 좋은 API를 만들 수 있다.
REST 원칙을 잘 이해하고 실천하여, 안정적이고 확장 가능한 웹 서비스를 구축하는 튼튼한 기반을 다지길 바란다.
댓글남기기