2025년 10월 28일 화요일

시니어 개발자가 알려주는 '잘 만든' RESTful API 설계 원칙 5가지






많은 주니어 개발자분이 기능 구현에는 익숙하지만, 'API 설계'를 하라고 하면 막막해하는 경우를 자주 봅니다.

코드를 작성하고 기능을 완성하는 것도 중요하지만, 우리가 만드는 API는 한번 만들면 나 혼자 쓰는 것이 아닙니다. 동료인 클라이언트 개발자, 프론트엔드 개발자, 심지어 다른 백엔드 서비스까지 함께 사용해야 하는 '공공재'이자 '설명서'입니다.

잘못 설계된 API는 사용하는 모든 사람에게 혼란을 주고, 버그를 유발하며, 장기적으로는 프로젝트 전체의 발목을 잡는 기술 부채가 됩니다. 반대로, 잘 만든 API는 그 자체로 훌륭한 문서가 되어 협업을 원활하게 하고 개발 속도를 높여줍니다. 이는 API가 서버와 클라이언트 간의 '계약(Contract)'이기 때문입니다. URI 구조, HTTP 메서드, 응답 형식 등은 이 계약서의 조항들이며, 한번 약속되면 함부로 바꿔서는 안 됩니다.

오늘은 "API 개발, 도대체 어떻게 해야 잘하는 걸까?"라는 근본적인 질문에 대한 답으로, '잘 만든 RESTful API'가 무엇인지 그 핵심 철학을 짚어보고, 현업에서 '이것만은 꼭 지켜야 할' 핵심 설계 원칙 5가지를 공유하고자 합니다. 이 글을 통해 '돌아가는 코드'를 넘어 '함께 일하기 좋은 코드'를 만드는 개발자로 성장하시길 바랍니다.







PART 1. API, 그리고 RESTful API란 무엇인가?



본격적인 원칙을 배우기 전에, 우리가 만들고자 하는 것의 본질부터 정확히 이해해야 합니다. API와 REST의 개념을 명확히 잡아봅시다.


API (Application Programming Interface)란?


가장 간단히 말해, API는 '프로그램(애플리케이션)끼리 소통하는 창구'입니다.

식당의 '키오스크'를 생각하면 이해가 쉽습니다.

손님 (Client): 음식을 주문하고 싶어 하는 사람입니다.


키오스크 (API): 손님이 주문을 넣을 수 있도록 만들어진 인터페이스입니다. 메뉴를 보여주고, 주문을 받고, 결제를 처리하는 표준화된 방법을 제공합니다.


주방 (Server): 실제 요리가 만들어지는 복잡한 공간입니다.

손님은 주방 내부가 어떻게 돌아가는지, 요리사가 몇 명인지, 레시피가 무엇인지 전혀 알 필요가 없습니다. 오직 키오스크 사용법만 알면 원하는 음식을 주문하고 받을 수 있습니다. 이처럼 API는 복잡한 내부 시스템을 감추고(이를 추상화라고 합니다), 외부 사용자가 정해진 규칙에 따라 시스템의 기능과 데이터에 접근할 수 있도록 해주는 약속이자 접점입니다. 덕분에 서버의 내부 로직이 변경되더라도, API라는 '계약'만 지켜진다면 클라이언트는 아무런 영향을 받지 않고 독립적으로 개발을 이어갈 수 있습니다.


REST (Representational State Transfer)란?

REST는 특정 기술이나 프로토콜이 아닙니다. API를 만드는 여러 방식 중, 현재 웹에서 가장 널리 사용되는 설계 스타일(아키텍처)입니다. 즉, '이렇게 만들면 웹의 장점을 잘 활용하는 좋은 API가 될 거야'라고 제시된 일종의 가이드라인입니다.

이름을 분해해보면 그 의미가 더 명확해집니다.

Representational (표현): 서버는 데이터베이스에 있는 자원(Resource) 그 자체를 보내는 것이 아니라, 그 자원의 상태에 대한 '표현(Representation)'을 보냅니다. 예를 들어, '사용자'라는 자원을 요청하면, 데이터베이스의 row가 아니라 그 사용자의 정보를 담은 JSON 객체를 보내주는 식입니다.


State Transfer (상태 전달): 클라이언트와 서버는 이 '자원의 표현'을 주고받으며 자원의 '상태(State)'를 변경하고 전달합니다.

이러한 REST 아키텍처 스타일의 제약 조건을 잘 따라서 만든 API를 'RESTful API'라고 부릅니다. REST가 웹 API의 표준으로 자리 잡은 가장 큰 이유는 '클라이언트와 서버의 분리(Decoupling)'를 촉진하기 때문입니다. 과거의 SOAP 같은 기술들은 서버와 클라이언트가 특정 기술에 강하게 얽혀 있어, 서버의 작은 변경이 클라이언트의 재배포를 요구하는 경우가 많았습니다. 하지만 REST는 HTTP라는 웹의 표준 프로토콜을 그대로 활용하기 때문에, 서로의 내부 구현을 전혀 몰라도 독립적으로 발전하고 확장할 수 있는 유연성을 제공합니다.






PART 2. '이것만은 지키자!' RESTful API 설계 5대 원칙


'잘 만든' API는 처음 보는 개발자도 그 기능을 쉽게 유추할 수 있을 만큼 직관적이고(Intuitive), 일관성이 있으며(Consistent), 예측 가능해야 합니다. 이를 위한 5가지 핵심 원칙을 소개합니다.


원칙 1. URI는 '자원(Resource)'을 표현해야 한다 (명사, 복수형)

가장 중요하고 기본적인 원칙입니다. REST의 핵심은 '자원' 중심입니다. 따라서 API의 URI는 '무엇을 할 것인가(행위)'가 아니라 '무엇을 다룰 것인가(자원)'를 중심으로 설계해야 합니다. 자원은 '명사'로 표현하는 것이 자연스럽습니다.

또한, 여러 자원의 모음, 즉 컬렉션(Collection)을 나타내는 URI에는 복수형 명사를 사용하는 것이 업계의 보편적인 약속(Convention)입니다. 이는 API의 예측 가능성을 크게 높여줍니다.

(나쁜 예시 👎)

/getUserInfo/1 (행위 'get'과 자원 'User'가 혼재)


/createProduct (행위 'create'가 URI에 포함됨)


/getPostsByUser/5 (자원 간의 관계를 행위로 표현하려는 흔한 실수)


(좋은 예시 👍)

/users/1 (users 라는 자원 모음 중, 1번 사용자를 지칭)


/products (모든 상품 자원의 모음)


/users/5/posts (5번 사용자가 작성한 모든 게시글. 자원 간의 관계를 계층적으로 표현)


원칙 2. '행위(Verb)'는 'HTTP 메서드'로 표현해야 한다

첫 번째 원칙에서 URI는 자원(명사)을 표현해야 한다고 했습니다. 그렇다면 '생성', '조회', '수정', '삭제'와 같은 '행위(동사)'는 어떻게 표현할까요? 바로 GET, POST, PUT, DELETE 같은 HTTP 메서드가 그 역할을 담당합니다.

HTTP 메서드 역할 (행위) 예시 URIPOST 생성 (Create) /users (사용자 1명 생성)
GET 조회 (Read) /users (모든 사용자 조회) /users/1 (1번 사용자 조회)
PUT / PATCH 수정 (Update) /users/1 (1번 사용자 수정)
DELETE 삭제 (Delete) /users/1 (1번 사용자 삭제)


여기서 더 나아가, 각 메서드의 중요한 특성인 멱등성(Idempotency)과 안전성(Safety)을 이해하면 훨씬 더 정교한 설계가 가능합니다.

안전성 (Safety): 해당 메서드를 호출해도 리소스의 상태가 변경되지 않음을 의미합니다. GET은 대표적인 안전한 메서드입니다.


멱등성 (Idempotency): 동일한 요청을 한 번 보내든, 여러 번 연속으로 보내든 결과가 똑같음을 의미합니다. GET, PUT, DELETE는 멱등성을 가집니다. 예를 들어, /users/1을 삭제하는 요청을 두 번 보내도 결과는 한 번 보낸 것과 같습니다. 하지만 POST는 멱등성이 없습니다. /users에 POST 요청을 두 번 보내면 두 명의 다른 사용자가 생성됩니다. 이 특성은 네트워크 오류로 인한 재시도 등을 구현할 때 매우 중요하게 작용합니다.

특히 주니어 개발자들이 자주 헷갈리는 것이 PUT과 PATCH의 차이입니다. 둘 다 '수정'이지만 그 방식이 다릅니다.
구분 PUT (전체 교체) PATCH (부분 수정)역할 자원의 전체 내용을 요청 본문(Body)으로 완전히 교체합니다. 요청에 포함되지 않은 필드는 null이나 초기값으로 변경될 수 있습니다. 자원의 일부 내용만 지정하여 수정합니다. 요청에 포함되지 않은 필드는 기존 값을 유지합니다.
멱등성 보장됨 (Idempotent). 동일한 내용으로 여러 번 요청해도 결과는 항상 동일합니다. 보장되지 않음 (Not Idempotent). (엄밀히 말해, 연산에 따라 결과가 달라질 수 있어 항상 보장되지는 않습니다.)
예시 PUT /users/1 {"name": "김철수", "email": "new@email.com"} (사용자 1의 모든 정보를 이걸로 덮어씁니다.) PATCH /users/1 {"email": "new@email.com"} (사용자 1의 email 필드만 변경하고, name은 그대로 둡니다.)


원칙 3. URI에는 동사 대신 '명사'를 사용하라 (재강조)

이 원칙은 새로운 내용이라기보다, 첫 번째와 두 번째 원칙을 합친 논리적 귀결입니다. 하지만 너무나도 중요하고 자주 실수하는 부분이기에 다시 한번 강조합니다. URI는 자원의 '이름표'일 뿐, 그 자체에 동사를 절대 포함해서는 안 됩니다.

스스로에게 이렇게 질문해보세요. "내가 만든 URI가 어떤 행동의 '결과'를 설명하는가, 아니면 '자원 그 자체'를 가리키는가?" 만약 URI에 동사가 있다면, 거의 확실하게 잘못된 길로 가고 있는 것입니다.

(나쁜 예시 👎) POST /posts/create


(좋은 예시 👍) POST /posts


(나쁜 예시 👎) GET /posts/delete/1


(좋은 예시 👍) DELETE /posts/1


원칙 4. 명확한 'HTTP 상태 코드'를 반환하라

API는 클라이언트의 요청에 대한 '처리 결과'를 숫자로 된 상태 코드로 명확하게 알려줘야 합니다. 이는 단순히 성공/실패 여부를 알려주는 것을 넘어, 결과에 대한 구체적인 정보를 담은 표준화된 약속입니다.

2xx (성공)

200 OK: 요청이 성공적으로 처리되었음을 알리는 가장 일반적인 코드입니다. (GET, PUT, PATCH 등의 성공 응답)


201 Created: POST 요청을 통해 새로운 자원이 성공적으로 생성되었을 때 사용합니다. 이때, 응답 헤더의 Location에 생성된 자원의 URI(Location: /users/123)를 포함해주는 것이 좋은 설계입니다.


204 No Content: 요청은 성공했지만, 응답으로 보낼 데이터가 없을 때 사용합니다. (DELETE 요청의 성공 응답 등)


4xx (클라이언트 오류): 요청 실패의 원인이 클라이언트에게 있음을 의미합니다.

400 Bad Request: 요청 자체가 잘못되었을 때 사용합니다. (예: 필수 파라미터 누락, JSON 형식 오류 등) 응답 본문에 구체적인 오류 원인을 명시해주는 것이 좋습니다.


401 Unauthorized: 인증되지 않은 사용자, 즉 로그인이 필요한데 로그인을 하지 않은 상태입니다. "당신이 누구인지 모르겠습니다." 라는 의미입니다.


403 Forbidden: 인증은 되었지만, 해당 자원에 접근할 권한이 없는 사용자입니다. "당신이 누구인지는 알지만, 이 자원에 접근할 권한은 없습니다." 라는 의미입니다.


404 Not Found: 요청한 자원(URI)이 서버에 존재하지 않을 때 사용합니다.


5xx (서버 오류): 요청은 유효했지만, 서버 내부에서 오류가 발생하여 처리하지 못했음을 의미합니다.

500 Internal Server Error: 서버 코드의 버그, 데이터베이스 오류 등 예기치 못한 문제가 발생했을 때 사용합니다. 보안을 위해 절대 서버의 스택 트레이스(Stack Trace) 같은 민감한 정보를 응답에 노출해서는 안 됩니다.

이처럼 표준 상태 코드를 잘 활용하면, 클라이언트 측에서는 응답 본문을 일일이 파싱하지 않고도 에러 핸들링 로직을 공통으로 만들어 재사용할 수 있습니다. 예를 들어, 모든 401 응답에 대해 자동으로 로그인 페이지로 리다이렉트하는 '인터셉터'를 구현하는 것이 가능해져 전체 애플리케이션의 안정성과 유지보수성이 크게 향상됩니다.





원칙 5. 응답 데이터는 일관된 형식을 사용하라 (JSON)

요청에 대한 응답 데이터는 JSON(JavaScript Object Notation) 형식을 사용하는 것이 사실상의 표준입니다. XML보다 가볍고, 사람이 읽기 쉬우며, 특히 웹 프론트엔드에서 주로 사용하는 JavaScript가 네이티브로 지원하기 때문입니다.

여기서 더 중요한 것은 '일관성'입니다.

Key 네이밍 규칙 (Casing Convention): 데이터의 'Key' 값은 카멜 케이스(camelCase) 또는 스네이크 케이스(snake_case) 중 하나를 프로젝트 차원에서 결정하고, 모든 API에서 일관되게 사용해야 합니다. userName과 user_name을 혼용하면 클라이언트 개발자에게 불필요한 혼란과 버그를 유발합니다. 보통 JavaScript 기반 생태계에서는 camelCase를, Python/Ruby 생태계에서는 snake_case를 선호하는 경향이 있습니다.

응답 구조 (Response Envelope): 모든 응답을 일관된 구조로 감싸주는 것도 좋은 방법입니다. 이렇게 하면 클라이언트가 성공과 실패에 대한 파싱 로직을 예측 가능하게 만들 수 있습니다.

성공 응답 예시: {"data": {"id": 1, "userName": "홍길동"}}


실패 응답 예시: {"error": {"code": "invalid_parameter", "message": "Email 필드는 비어 있을 수 없습니다."}}





PART 3. 시니어 개발자의 보너스 팁 (Versioning & Filtering)


위의 5가지 원칙이 튼튼한 기본기라면, 지금부터 소개할 팁들은 여러분의 API를 기본 수준에서 '성숙한 상용 서비스 수준'으로 끌어올려 줄 실전 기술입니다.

버전 관리 (Versioning)

API는 살아있는 생물과 같아서 계속해서 변하고 진화합니다. 새로운 기능이 추가되고, 데이터 구조가 바뀌고, 때로는 기존 기능이 제거되기도 합니다. 이때 아무런 대책 없이 API를 수정하면, 기존 버전을 사용하던 클라이언트 앱들이 모두 '먹통'이 될 수 있습니다. 버전 관리는 이러한 '파괴적인 변경(Breaking Change)'으로부터 기존 사용자들을 보호하며 안전하게 API를 업데이트할 수 있게 해주는 필수적인 전략입니다.

가장 보편적이고 직관적인 방법은 URI에 버전을 명시하는 것입니다.

https://api.example.com/v1/users


https://api.example.com/v2/users

이렇게 하면 v1을 사용하던 클라이언트는 그대로 두고, 새로운 기능이 추가된 v2를 개발하여 신규 클라이언트나 마이그레이션을 원하는 클라이언트에게 제공할 수 있습니다.

필터링, 정렬, 그리고 페이지네이션 (Filtering, Sorting, and Pagination)

클라이언트가 항상 자원의 전체 목록을 필요로 하는 것은 아닙니다. '최신순으로 정렬된 게시글 20개' 또는 '판매 중인 상품만'처럼 특정 조건에 맞는 데이터만 원하는 경우가 대부분입니다. 이를 위해 /posts/recent, /products/on-sale처럼 수많은 URI를 만드는 것은 REST 철학에 어긋나며 확장성도 떨어집니다.

더 좋은 방법은 쿼리 파라미터(Query Parameter)를 활용하여 클라이언트가 원하는 데이터의 '표현'을 스스로 정제할 수 있도록 유연성을 제공하는 것입니다.

예시: GET /posts?status=published&author_id=5&sort=-createdAt&page=2&limit=20

이 URI를 분석해보면 다음과 같습니다.

필터링 (Filtering): status=published&author_id=5 (상태가 '발행됨'이고, 작성자 ID가 5인 게시글만)


정렬 (Sorting): sort=-createdAt (생성일(createdAt)을 기준으로 내림차순 정렬. - 접두사는 내림차순을 의미하는 일반적인 컨벤션입니다.)


페이지네이션 (Pagination): page=2&limit=20 (한 페이지에 20개씩, 그중 2번째 페이지의 데이터를 요청)

이 방식이야말로 '자원 중심 설계' 철학의 정수입니다. /posts라는 자원의 본질은 변하지 않으면서, 쿼리 파라미터를 통해 그 자원을 어떻게 보여줄지만을 클라이언트가 제어할 수 있게 됩니다. 이는 API를 훨씬 더 강력하고 확장 가능하게 만들어 줍니다.




 '좋은 API'는 최고의 '협업 도구'입니다

'잘 만든 API'는 단순히 잘 동작하는 코드를 넘어, 그 자체로 가장 훌륭한 문서(Documentation)이자 최고의 협업 도구입니다. 명확하고 일관된 API는 프론트엔드 개발자와 백엔드 개발자 간의 불필요한 소통 비용을 줄여주고, 오해를 막아주며, 결과적으로 더 빠르고 안정적인 제품 개발로 이어집니다.

오늘 배운 원칙들은 단순히 지켜야 할 규칙 목록이 아닙니다. 함께 일하는 동료를 배려하고, 미래의 나 자신을 돕는 이정표입니다. 이 원칙들을 바탕으로, 나뿐만 아니라 함께 일하는 프론트엔드 개발자, 동료 백엔드 개발자 모두가 행복해지는 '읽기 쉬운 API'를 만드시길 진심으로 바랍니다.

#API개발 #RESTAPI #백엔드개발자 #개발자블로그 #API설계










노블발렌티 삼성, 단순한 예식장이 아닌 '시스템'을 분석하다

강남에서 웨딩홀을 알아보다 보면 선택의 홍수 속에 빠지기 쉽습니다. 특히 '채플 웨딩'과 '하우스 웨딩'의 장점을 결합한 곳을 찾는다면 선택지는 더욱 좁아집니다. 인생의 가장 중요한 순간을 맡길 장소라면 단순한 화려함을 넘...