스터디 기록

스터디 2 - [Step 1] REST API 기초: HTTP 메서드로 CRUD 구현하기

rnaster 2025. 11. 18. 21:58

들어가며

Spring Boot로 웹 애플리케이션을 만들 때 가장 먼저 접하는 것이 REST API입니다. API는 프론트와 서버간 통신을 통해 소통할 수 있는 창구 입니다. 이 글에서는 REST API의 핵심 개념과 HTTP 메서드를 사용해 기본적인 CRUD를 구현하는 방법을 다룹니다. 

 

1. REST API란?

REST API는 HTTP를 사용해 데이터를 주고받는 웹 API 설계 방식입니다.

핵심 아이디어:

  • 자원(Resource) 을 URL로 표현: /books, /books/1
  • 행위(CRUD) 를 HTTP 메서드로 표현: GET, POST, PUT, DELETE 등
  • 데이터를 JSON으로 주고받음

구조:

[웹 브라우저/앱]  ─HTTP 요청→  [Spring Boot API]
  (클라이언트)    ←HTTP 응답─   (우리가 만드는 것)

클라이언트는 URL과 HTTP 메서드로 요청하고, 서버는 JSON 데이터로 응답합니다.

 

REST API, 왜 필요한가?

웹 애플리케이션을 만들 때 두 가지 방식이 있습니다. 첫 번째는 서버에서 HTML을 만들어 보내는 전통적인 방식(JSP, Thymeleaf 같은)이고, 두 번째는 서버는 데이터만 보내고 화면은 프론트엔드가 만드는 방식입니다. REST API는 후자입니다.

왜 이렇게 분리할까요? 하나의 백엔드 API로 웹 브라우저, 모바일 앱, 데스크톱 앱을 모두 지원할 수 있기 때문입니다. 프론트엔드 팀과 백엔드 팀이 동시에 개발할 수 있고, 각자 독립적으로 배포할 수도 있습니다.

 

 

REST API의 핵심 아이디어는 단순합니다. 자원(책, 회원, 주문 등)을 URL로 표현하고, 그 자원에 대한 행위(조회, 생성, 수정, 삭제)를 HTTP 메서드로 표현합니다. 그리고 데이터는 JSON으로 주고받습니다.

 

2. HTTP 메서드로 CRUD 구현

2.1 HTTP 메서드와 CRUD

작업HTTP 메서드URL 예시

Create (생성) POST POST /books
Read (조회) GET GET /books, GET /books/1
Update (수정) PUT, PATCH PUT /books/1
Delete (삭제) DELETE DELETE /books/1

 

2.2 각 메서드의 특징

GET - 조회

GET /books           # 전체 목록
GET /books/1         # ID 1번 조회
GET /books?author=오다  # 검색
  • 데이터를 변경하지 않음
  • 여러 번 호출해도 결과가 같음 (멱등성)
  • 브라우저 주소창에 직접 입력 가능

 

POST - 생성

POST /books
Content-Type: application/json

{
  "title": "원피스 1권",
  "author": "오다 에이이치로",
  "stock": 5
}
  • 새로운 데이터 생성
  • 같은 요청을 반복하면 데이터가 여러 개 생성됨

 

PUT - 전체 수정

PUT /books/1
Content-Type: application/json

{
  "title": "원피스 1권",
  "author": "오다 에이이치로",
  "stock": 10
}
  • 데이터 전체를 교체
  • 모든 필드를 보내야 함
  • 여러 번 호출해도 결과가 같음 (멱등성)

 

PATCH - 부분 수정

PATCH /books/1
Content-Type: application/json

{
  "stock": 10
}
  • 일부 필드만 수정
  • 변경할 필드만 전송

 

DELETE - 삭제

DELETE /books/1
  • 데이터 삭제
  • 여러 번 호출해도 결과가 같음 (멱등성)

멱등성(Idempotent): 같은 요청을 여러 번 해도 결과가 동일한 성질. 네트워크 오류로 요청이 중복돼도 안전합니다.

 

멱등성, 왜 중요한가?

여기서 중요한 개념이 하나 나옵니다. 바로 멱등성(Idempotent)입니다. 같은 요청을 여러 번 해도 결과가 같은 성질을 말합니다. GET, PUT, DELETE는 멱등하지만, POST는 멱등하지 않습니다.

실무에서 왜 이게 중요할까요? 네트워크는 언제나 불안정합니다. 사용자가 "재고 확보" 버튼을 눌렀는데 서버는 처리를 완료했지만 네트워크 지연으로 응답이 안 올 수 있습니다. 클라이언트는 타임아웃으로 판단하고 다시 요청을 보냅니다.

이때 PUT이었다면 안전합니다. PUT /books/1 {"stock": 10}을 두 번 보내도 재고는 10입니다. 하지만 만약 POST로 설계했다면? 같은 책이 2개 생길 수 있습니다. 결제 API를 POST로 만들었는데 중복 클릭이 발생하면 어떨까요? 고객이 두 번 결제될 수 있습니다.

 

모바일 앱에서는 이런 일이 더 자주 일어납니다. 네트워크가 불안정한 환경에서 사용자가 버튼을 여러 번 누를 수 있기 때문입니다. 그래서 실무에서는 POST 요청에 대해서는 중복 방지 로직을 별도로 구현합니다. idempotency key라는 걸 함께 보내서 같은 요청인지 판별하는 식이죠.

GET, PUT, DELETE는 멱등하기 때문에 자동 재시도 로직을 안전하게 적용할 수 있습니다. AWS 같은 클라우드 서비스의 로드 밸런서도 이 특성을 활용합니다. 이것이 멱등성이 단순한 이론이 아니라 실전 설계에서 중요한 이유입니다.

 

3. HTTP 상태 코드

 

HTTP 상태 코드, 꼭 써야 하나?

처음 API를 만들 때 이런 생각을 할 수 있습니다. "굳이 상태 코드를 신경 써야 하나? 그냥 다 200 OK로 보내고, body에 success: true/false 넣으면 되지 않나?"

하지만 실무에서는 HTTP 상태 코드를 제대로 쓰는 게 정말 중요합니다. 세 가지 이유가 있습니다.

첫째, 프론트엔드와의 협업

프론트엔드 개발자는 HTTP 상태 코드를 보고 빠르게 판단합니다. 200번대면 성공, 400번대면 클라이언트 오류, 500번대면 서버 오류. 코드만 보고 바로 분기 처리를 할 수 있습니다.

 
 
javascript
fetch('/books/999')
  .then(response => {
    if (response.ok) {  // 200-299 범위
      // 성공 처리
    } else if (response.status === 404) {
      // 없는 책입니다
    } else if (response.status === 500) {
      // 서버 오류입니다
    }
  })

만약 모든 응답이 200이라면? body를 열어서 success 필드를 확인하고, error 필드가 있는지 확인하고... 매번 이런 과정을 거쳐야 합니다. 비효율적이죠.

둘째, 로그 분석과 모니터링

운영 중인 서비스의 로그를 봤을 때, HTTP 상태 코드로 한눈에 파악할 수 있습니다. 404가 많이 보이면 "클라이언트가 잘못된 URL로 요청하고 있구나", 500이 많으면 "서버에 문제가 있구나" 하고 즉시 알 수 있습니다.

모니터링 시스템(예: Datadog, CloudWatch)도 HTTP 상태 코드를 기준으로 알림을 설정합니다. 500 에러가 10번 발생하면 알림을 보내라, 이런 식으로요. 모든 응답이 200이면 이런 모니터링이 불가능합니다.

셋째, HTTP 표준

HTTP 상태 코드는 웹의 표준입니다. 전 세계 개발자들이 같은 규칙으로 소통합니다. 201은 생성됨, 204는 내용 없음, 404는 찾을 수 없음. 이 의미를 모두가 알고 있기 때문에 API 문서를 자세히 읽지 않아도 대충 감을 잡을 수 있습니다.

3.1 성공 응답 (2xx)

200 OK 성공 GET, PUT, PATCH 성공
201 Created 생성됨 POST 성공
204 No Content 내용 없음 DELETE 성공

3.2 클라이언트 오류 (4xx)

400 Bad Request 잘못된 요청 입력값 검증 실패
401 Unauthorized 인증 필요 로그인 필요
403 Forbidden 권한 없음 권한 부족
404 Not Found 없음 요청한 데이터가 없음

3.3 서버 오류 (5xx)

500 Internal Server Error 서버 오류

 

4. URL 설계 원칙

 

REST API를 배우면 URL 설계 원칙이 꽤 많이 나옵니다. 명사를 써라, 동사를 쓰지 마라, 복수형을 써라. 이런 규칙은 실은 상식적입니다. 상식이란 범 사회적인 합의처럼 보입니다. 하지만 그렇지 않습니다. 정말 많은 사람들이 엄청난 고통을 겪고 난 뒤에 깨닳음을 얻고, 그 결과로 사회적 합의가 이루어지고 긴 시간이 지나면서 합의에 대한 원형은 사라지고 결과만 남아 왜 이렇게 해야 하는지 모른채 그냥 따라가게 되는 양상을 띕니다. 안전 수칙 같은 것들이 이에 해당합니다.

예측 가능성

RESTful한 URL은 패턴이 일관적입니다. /books를 보면 "아, 책 목록이 있겠구나", /books/1을 보면 "1번 책 상세 정보겠구나", /books/1/rentals를 보면 "1번 책의 대여 기록이겠구나" 하고 바로 예측할 수 있습니다. url을 읽는 사용자가 직관적으로 받아들이게 하는 것이 RESTful 의 핵심 키워드입니다. (잘 만든 변수명, 함수명, 클래스명이 이에 해당합니다. 개인적으로 IT대기업 소스코드는 읽자마자 바로 도메인 로직을 이해할 수 있었습니다. 일 잘하는 개발자들이란 이런 것이겠죠.)

반면 일관성 없는 설계는 어떨까요? /getBookList, /bookDetail?id=1, /rental_history_of_book?book_id=1. 각각 뭘 어떻게 호출해야 하는지 매번 문서를 찾아야 합니다. API가 100개, 200개가 되면 이런 불편함이 기하급수적으로 커집니다.

커뮤니케이션 비용

실무에서 백엔드 개발자와 프론트엔드 개발자는 계속 대화합니다. "저기요, 책 조회 API 어떻게 호출해요?" "GET /books/1 하면 돼요." 명확한 규칙이 있으면 이런 대화가 간결해집니다.

규칙이 없으면 어떨까요? "저기요, 그 책 상세 보는 거 URL이 뭐였죠?" "음... 어디 보자, bookDetail이었나 getBookInfo였나..." 매번 이런 대화를 나누게 됩니다. API가 많아질수록 이런 질문이 늘어나고 팀 전체의 생산성이 떨어집니다.

유지보수

6개월 후 코드를 다시 열었을 때를 생각해봅시다. DELETE /books/1을 보면 "아, 1번 책을 삭제하는구나" 하고 바로 이해됩니다. 하지만 POST /removeBook?bookId=1을 보면 "어? POST인데 삭제를? 왜지?" 하고 헷갈립니다.

본인이 짠 코드도 시간이 지나면 잊어버립니다. 하물며 다른 사람이 짠 코드를 인수인계 받았을 때는 어떨까요? 명확한 규칙이 있으면 코드를 처음 보는 사람도 빠르게 이해할 수 있습니다.

확장성

일관된 패턴의 또 다른 장점은 확장 시에도 그대로 적용된다는 겁니다. 책 API를 /books로 만들었다면, 회원 API는 /members, 대여 API는 /rentals로 자연스럽게 확장됩니다. 규칙을 한 번 배우면 모든 리소스에 적용할 수 있습니다.

프론트엔드 개발자 입장에서도 마찬가지입니다. 책 API를 다루는 방법을 익혔으면 회원 API, 대여 API도 같은 방식으로 다룰 수 있습니다. 학습 비용이 줄어드는 거죠.

 

4.1 명사 사용, 동사 금지

URL은 리소스(명사), 행위는 HTTP 메서드로 표현

✅ 좋은 예
GET    /books          # 조회
POST   /books          # 생성
GET    /books/1        # 1번 조회
PUT    /books/1        # 1번 수정
DELETE /books/1        # 1번 삭제

❌ 나쁜 예
GET    /getBooks       # 동사 사용
POST   /createBook     # 동사 사용
POST   /books/delete/1 # 동사 사용

4.2 복수형 사용

✅ /books
✅ /members
✅ /rentals

❌ /book
❌ /member

4.3 계층 구조

/libraries/1/books      # 1번 도서관의 책 목록
/members/10/rentals     # 10번 회원의 대여 목록

너무 깊은 계층(4단계 이상)은 피합니다.

4.4 쿼리 파라미터 활용

# 검색
GET /books?author=오다

# 페이징
GET /books?page=1&size=20

# 정렬
GET /books?sort=title

 

5. API 문서화 (Swagger로 문서 자동화 하기)

 

업무에서 API는 혼자 만드는 게 아닙니다. 프론트엔드 개발자, 기획자, 디자이너와 함께 만듭니다. 협업 과정을 이해하면 왜 REST API를 이렇게 설계하는지 더 잘 이해할 수 있습니다.

5.1 API 우선 설계

일반적인 흐름은 이렇습니다. 기획자가 "책 관리 기능이 필요합니다"라고 하면, 개발자들이 모여서 어떤 API가 필요한지 먼저 설계합니다.

"책 목록 보여줘야 하니까 GET /books 필요하고, 상세 정보는 GET /books/{id}, 등록은 POST /books..." 이런 식으로 엔드포인트를 정합니다. 그리고 각 API의 Request와 Response 형식을 합의합니다.

GET /books/1
Response:
{
  "id": 1,
  "title": "원피스 1권",
  "author": "오다 에이이치로",
  "stock": 5
}

 

이렇게 API 명세가 정해지면 프론트엔드와 백엔드가 동시에 개발을 시작합니다. 프론트엔드는 위 JSON 구조로 Mock 데이터를 만들어서 화면을 먼저 만듭니다. 백엔드는 실제로 데이터베이스와 연동해서 API를 구현합니다.

개발이 끝나면 통합합니다. 프론트엔드는 Mock API 주소를 실제 API 주소로 바꾸기만 하면 됩니다. 

5.2 Swagger로 문서 자동화

API가 10개, 20개가 되면 문서 관리가 힘들어집니다. 워드나 노션에 정리하다 보면 코드는 바뀌었는데 문서는 업데이트를 안 해서 정보가 틀어지는 일이 발생합니다.

이럴 때 사용하는 게 Swagger 입니다. Spring Boot 코드에 간단한 애너테이션만 추가하면 API 문서가 자동으로 생성됩니다. 그리고 이 문서는 코드와 항상 동기화됩니다.

Swagger UI에 접속하면 이런 걸 볼 수 있습니다:

  • 모든 API 엔드포인트 목록
  • 각 API가 받는 파라미터
  • Request Body 형식
  • Response 형식과 예시
  • "Try it out" 버튼을 눌러서 바로 테스트

프론트엔드 개발자 입장에서는 Swagger 문서만 보고 모든 게 해결됩니다. 백엔드 개발자에게 "이 API 어떻게 호출해요?"라고 물어볼 필요가 없습니다. 직접 테스트도 해볼 수 있고요.

백엔드 개발자 입장에서는? 코드만 잘 짜면 문서는 자동으로 생성되니까 편합니다. "문서 업데이트 좀 해주세요"라는 말을 들을 일이 없어집니다.

 

이 글을 작성하는 시점에는 Step1 스터디를 진행하기 전입니다. 스터디를 진행하며 진행 속도와 질문 및 추가로 설명이 필요한 사항들을 정리하겠습니다. 이 스터디 자료는 실습 파트를 따로 진행하고 있습니다. 실습 진행을 위한 별도의 환경 구성이 생략되었습니다.