Spring으로 백엔드 서버를 만들기 전에 먼저 이해해야 하는 것은 HTTP의 요청과 응답 흐름이다. Spring Controller에서 사용하는 @GetMapping, @PostMapping, @RequestBody, @PathVariable, ResponseEntity는 모두 HTTP 구조 위에서 동작한다.
따라서 이 문서는 HTTP가 어떤 방식으로 클라이언트와 서버를 연결하는지, HTTP Method와 상태 코드는 각각 어떤 역할을 하는지, API와 RESTful 설계가 Spring 개발에서 어떻게 연결되는지를 정리한다.
클라이언트가 어떤 요청을 보내는가
→ 서버가 어떤 기준으로 처리하는가
→ 어떤 상태 코드와 데이터로 응답하는가
위 흐름을 이해해야 Spring 코드를 볼 때 "어노테이션을 외우는 것"이 아니라 "HTTP 요청을 Java 메서드에 연결하는 것"으로 이해할 수 있다.
| 구분 | 내용 |
|---|---|
| HTTP 통신 구조 | Request, Response, Stateless, HTTP Message 구조 |
| HTTP Method | GET, POST, PUT, PATCH, DELETE |
| Method 속성 | 안전성, 멱등성, 캐시가능성 |
| HTTP 상태 코드 | 2xx, 3xx, 4xx, 5xx와 대표 코드 |
| API | 클라이언트와 서버가 통신하는 인터페이스 |
| RESTful API | 예측 가능한 URL 설계 방식 |
| Spring 연결 | HTTP 개념이 Controller 코드로 연결되는 방식 |
HTTP는 클라이언트와 서버가 요청과 응답을 주고받기 위한 통신 규칙이다. 클라이언트는 HTTP Method와 URL을 이용해 서버에 요청하고, 서버는 처리 결과를 상태 코드와 응답 Body로 돌려준다.
API는 클라이언트와 서버가 어떤 방식으로 요청하고 응답할지 정한 인터페이스이다. 클라이언트는 서버 내부가 Spring으로 만들어졌는지, Java로 만들어졌는지 알 필요 없이 API 명세만 지키면 된다.
RESTful API는 API를 더 예측 가능하게 만들기 위한 설계 방식이다. URL은 자원을 표현하고, 행동은 HTTP Method로 표현한다.
Spring Controller는 이 HTTP 요청을 Java 메서드와 연결해주는 역할을 한다.
HTTP
= 클라이언트와 서버가 대화하는 규칙
HTTP Method
= 클라이언트가 원하는 행동
HTTP Status Code
= 서버가 돌려주는 처리 결과
API
= 클라이언트와 서버가 약속한 사용 방법
RESTful API
= API를 읽기 쉽고 예측 가능하게 설계하는 방식
Spring Controller
= HTTP 요청을 Java 메서드와 연결하는 지점
| 개념 | 핵심 질문 |
|---|---|
| HTTP | 클라이언트와 서버는 어떻게 대화하는가? |
| Method | 클라이언트가 어떤 행동을 요청하는가? |
| Status Code | 서버는 처리 결과를 어떻게 알려주는가? |
| API | 서로 어떤 약속으로 요청하고 응답하는가? |
| RESTful | API 주소를 어떻게 읽기 쉽게 설계하는가? |
| Spring Controller | 이 흐름이 코드에서 어떻게 연결되는가? |
웹에서 클라이언트와 서버가 데이터를 주고받기 위한 통신 규칙.
비유하면 식당 주문 방식과 비슷하다. 손님이 직원에게 "김치찌개 하나 주세요"라고 요청하면, 직원은 주문을 주방에 전달하고 음식이 준비되면 손님에게 가져다준다.
웹에서는 이 구조가 다음과 같이 바뀐다.
| 식당 비유 | 웹에서의 의미 |
|---|---|
| 손님 | 클라이언트 |
| 주방 | 서버 |
| 주문 | Request |
| 음식 제공 | Response |
| 주문 방식 | HTTP |
HTTP의 기본 흐름은 단순하다.
클라이언트가 요청을 보낸다.
→ 서버가 요청을 처리한다.
→ 서버가 처리 결과를 응답한다.
예를 들어 사용자가 게시글 목록을 보고 싶다면 클라이언트는 다음과 같은 요청을 보낼 수 있다.
GET /posts
서버는 요청을 처리한 뒤 게시글 목록 데이터를 응답한다.
[
{
"id": 1,
"title": "첫 번째 게시글"
},
{
"id": 2,
"title": "두 번째 게시글"
}
]
한 줄 정리:
HTTP는 클라이언트와 서버가 정해진 형식으로 요청과 응답을 주고받는 대화 방식이다.
HTTP는 Stateless하다. 즉, 서버가 이전 요청을 기본적으로 기억하지 않는다.
HTTP의 중요한 특징 중 하나는 Stateless이다.
Stateless는 서버가 클라이언트의 이전 상태를 기본적으로 보존하지 않는다는 뜻이다.
비유하면 저장 기능이 없는 게임과 비슷하다.
게임을 하다가 종료한 뒤 다시 접속했는데 이전 점수나 진행 상황이 남아 있지 않다면 처음부터 다시 시작해야 한다. HTTP도 기본적으로는 요청 하나하나를 독립적으로 처리한다.
예를 들어 클라이언트가 다음 요청을 보냈다고 하자.
GET /products
그 다음 다시 이런 요청을 보낸다:
GET /cart
HTTP 자체만 높고 보면 서버는 이전에 /products를 요청했던 사용자인지 자동으로 기억하지 않는다. 요청은 각각 독립적으로 처리된다.
이 구조는 장점도 있다. 서버가 사용자의 상태를 계속 들고 있지 않아도 되기 때문에 서버를 여러 대로 늘리는 수평 확장에 유리하다. 하지만 단점도 있다. 로그인처럼 사용자의 상태를 유지해야 하는 기능은 HTTP만으로 해결할 수 없다.
그래서 로그인 상태를 유지할 때는 추가 장치가 필요하다.
| 방식 | 역할 |
|---|---|
| Cookie | 클라이언트 쪽에 작은 정보를 저장 |
| Session | 서버 쪽에 로그인 상태를 저장 |
| Token | 클라이언트가 요청마다 로그인 증명서를 함께 보냄 |
HTTP는 기본적으로 이전 요청을 기억하지 않는다.
그래서 로그인 상태를 유지하려면 Cookie, Session, Token 같은 별도 장치가 필요하다.
HTTP 메시지는 Start Line, Header, Empty Line, Body로 나뉜다.
HTTP 메시지는 요청 메시지와 응답 메시지로 나뉜다.
요청 메시지는 클라이언트가 서버로 보내는 메시지이고,
응답 메시지는 서버가 클라이언트에게 돌려주는 메시지이다.
POST /members HTTP/1.1
Host: example.com
Content-Type: application/json
{
"name": "kim",
"age": 20
}
| 구성 요소 | 의미 |
|---|---|
| Start Line | 어떤 Method로 어떤 경로에 요청할지 |
| Header | 요청에 대한 추가 정보 |
| Empty Line | Header와 Body를 구분하는 공백 줄 |
| Message Body | 실제 전송할 데이터 |
요청 메세지에서 Start Line은 요청의 핵심 정보를 담는다.
POST /members HTTP/1.1
이 한 줄에는 다음 의미가 들어 있다.
| 부분 | 의미 |
|---|---|
| POST | 새 리소스를 생성하거나 데이터를 처리해달라는 요청 |
| /members | 요청 대상 |
| HTTP/1.1 | HTTP 버전 |
Header는 요청에 대한 부가 정보를 담는다. 예를 들어 Content-Type: application/json은 Body에 담긴 데이터가 JSON 형식이라는 뜻이다.
처음에는 Empty Line이 단순한 공백처럼 보일 수 있다.
하지만 HTTP Message 구조에서는 Header와 Body를 나누는 기준이다.
Content-Type: application/json
{
"name": "kim"
}
빈 줄 위는 Header이고, 빈 줄 아래는 Body이다.
즉, Empty Line은 이런 역할을 한다.
여기까지는 요청 설명이다.
이제부터 실제 데이터가 온다.
Empty Line은 단순한 여백이 아니라 Header와 Body를 구분하는 필수 구분선이다.
HTTP/1.1 201 Created
Content-Type: application/json
{
"id": 1,
"name": "kim"
}
| 구성 요소 | 의미 |
|---|---|
| Start Line | HTTP 버전, 상태 코드, 상태 문구 |
| Header | 응답에 대한 추가 정보 |
| Empty Line | Header와 Body를 구분 |
| Message Body | 실제 응답 데이터 |
응답 메세지에서 중요한 부분은 상태 코드이다.
HTTP/1.1 201 Created
201 Created는 요청이 성공했고, 새로운 리소스가 생성되었다는 뜻이다.
Spring에서는 HTTP 메시지를 직접 문자열로 작성하지 않는다. 하지만 @RequestBody, @ResponseBody, ResponseEntity 같은 기능은 모두 이 HTTP Message 구조를 기반으로 동작한다.

Method는 클라이언트가 서버에게 원하는 행동을 표현한다.
HTTP Method는 요청의 의도를 나타낸다.
주소가 "대상"이라면, Method는 "행동"이다.
URL = 무엇을 대상으로?
Method = 어떤 행동을?
식당 비유로 보면 다음과 같다.
| 식당에서 하는 말 | HTTP Method |
|---|---|
| 메뉴판 보여주세요 | GET |
| 김치찌개 하나 만들어주세요 | POST |
| 주문한 메뉴를 통째로 바꿔주세요 | PUT |
| 맵기만 바꿔주세요 | PATCH |
| 주문 취소할게요 | DELETE |
| Method | 의미 | 예시 |
|---|---|---|
| GET | 조회 | 회원 조회, 게시글 목록 조회 |
| POST | 생성 또는 처리 | 회원가입, 게시글 작성, 주문 생성 |
| PUT | 전체 수정 | 회원 정보 전체 교체 |
| PATCH | 일부 수정 | 주소만 변경 |
| DELETE | 삭제 | 게시글 삭제 |
| HEAD | Body 없이 Header만 조회 | 응답 정보만 확인 |
| OPTIONS | 가능한 통신 방식 확인 | 지원 Method 확인 |
GET은 서버의 데이터를 조회할 때 사용한다.
GET /members/1
이 요청은 "1번 회원 정보를 조회해줘" 라는 의미이다. 조회 요청이기 때문에 서버 데이터를 변경하지 않는다.
POST는 서버에 데이터를 보내서 새 리소스를 만들거나 특정 처리를 요청할 때 사용한다.
POST /members
{
"name": "kim",
"age": 20
}
이 요청은 "이 정보를 바탕으로 새 회원을 생성해줘" 라는 의미이다.
정리하면 다음과 같다.
| 구분 | GET | POST |
|---|---|---|
| 목적 | 조회 | 생성 또는 처리 |
| Body 사용 | 보통 사용하지 않음 | 자주 사용 |
| 예시 | 회원 조회, 게시글 목록 조회 | 회원가입, 게시글 작성, 주문 생성 |
| 서버 데이터 변경 | 없음 | 있을 수 있음 |
단순 조회, 수정, 삭제로 표현하기 어려운 처리성 요청은 POST를 사용할 수 있다.
하지만 모든 요청을 POST로 처리하는 것은 좋은 설계가 아니다.
먼저 이 질문을 해야 한다.
이 요청은 조회인가?
생성인가?
전체 수정인가?
일부 수정인가?
삭제인가?
PUT과 PATCH는 모두 수정에 사용되지만 의미가 다르다.
PUT은 리소스를 전체 교체하는 방식이다. PATCH는 리소스의 일부만 수정하는 방식이다.
기존 회원 정보가 다음과 같다고 하자:
{
"name": "kim",
"age": 20,
"address": "seoul"
}
PUT은 전체를 다시 보내서 교체하는 느낌이다.
PUT /members/1
{
"name": "lee",
"age": 21,
"address": "busan"
}
PATCH는 일부 필드만 바꾸는 느낌이다.
PATCH /members/1
{
"address": "busan"
}
| Method | 핵심 의미 | 예시 |
|---|---|---|
| PUT | 전체 수정, 덮어쓰기 | 회원 정보를 통째로 교체 |
| PATCH | 일부 수정 | 주소만 변경 |
PUT은 전체 교체, PATCH는 일부 수정이다.
Method 속성은 요청을 안전하게 재시도하거나 저장해도 되는지 판단하는 기준이다.
HTTP Method에는 세 가지 중요한 속성이 있다.
| 속성 | 판단 질문 |
|---|---|
| 안전성 | 서버 데이터를 바꾸지 않는가? |
| 멱등성 | 같은 요청을 여러 번 보내도 최종 결과가 같은가? |
| 캐시가능성 | 응답을 저장해두고 재사용할 수 있는가? |
안전성은 요청을 보내도 서버의 데이터가 변경되지 않는 성질이다.
GET /posts/1
이 요청은 1번 게시글을 조회할 뿐이다. 게시글을 생성하거나 수정하거나 삭제하지 않는다. 그래서 GET은 안전하다.
반대로 다음 요청들은 안전하지 않다.
POST /posts
PUT /posts/1
PATCH /posts/1
DELETE /posts/1
이 요청들은 서버 데이터를 생성, 수정, 삭제할 수 있기 때문이다.
주의할 점은 안전성이 "에러가 절대 안 난다"는 뜻이 아니라는 것이다. 안전성은 서버 데이터가 변경되지 않는지는 기준으로 판단한다.
멱등성은 같은 요청을 한 번 보내든 여러번 보내던 최종 결과가 같은 성질이다.
비유하면 엘리베이터의 3층 버튼과 비슷하다.
3층 버튼을 한 번 누름 → 3층으로 감
3층 버튼을 다섯 번 누름 → 그래도 3층으로 감
최종 결과는 같다.
이것이 멱등성이다.
반대로 송금 버튼은 다르다.
10만 원 송금 버튼 1번 클릭 → 10만 원 송금
10만 원 송금 버튼 2번 클릭 → 20만 원 송금 가능
이런 요청은 멱등하지 않다.
| Method | 멱등성 | 이유 |
|---|---|---|
| GET | O | 여러 번 조회해도 최종 상태는 같음 |
| PUT | O | 같은 내용으로 여러 번 덮어써도 최종 상태는 같음 |
| DELETE | O | 여러 번 삭제 요청해도 최종 상태는 삭제된 상태 |
| POST | X | 여러 번 요청하면 글, 주문, 회원이 중복 생성될 수 있음 |
| PATCH | 보통 X | 수정 방식에 따라 결과가 달라질 수 있음 |
멱등성이 중요한 이유는 재시도 때문이다.
요청 실패
→ 같은 요청을 다시 보내도 되는가?
GET은 다시 보내도 보통 괜찮다.
GET /members/1
하지만 주문 생성 요청은 다르다.
POST /orders
사용자가 주문 버튼을 두 번 누르거나 서버가 자동 재시도를 잘못 처리하면 주문이 중복 생성될 수 있다.
멱등성은 장애 상황에서 “다시 요청해도 되는가?”를 판단하는 기준이다.
캐시는 이전 응답을 임시 저장해두고 재사용하는 방식이다.
처음에는 캐시가 단순히 "어딘가에 저장하는 것"처럼 느껴질 수 있다.
하지만 핵심은 서버 요청을 줄이는 것이다.
웹사이트 로고, CSS파일, 이미지, JavaScript 파일은 자주 바뀌지 않는다.
사용자가 페이지를 열 때마다 서버에서 전부 다시 받아오면 시간도 걸리고 서버도 바빠진다.
그래서 한 번 받은 데이터를 잠시 보관해두고 다시 사용한다.
비유하면 단골 식당과 비슷하다.
| 상황 | 캐시 개념 |
|---|---|
| 처음 방문해서 메뉴판을 봐야 함 | Cache MISS |
| 단골이라 항상 먹던 메뉴를 바로 주문함 | Cache HIT |
| 구분 | 의미 |
|---|---|
| Cache HIT | 캐시에 데이터가 있어서 서버 요청 없이 사용 |
| Cache MISS | 캐시에 데이터가 없어서 서버에 요청한 뒤 저장 |
| memory cache | 메모리에 저장된 캐시 |
| disk cache | 디스크에 저장된 캐시 |
캐시 흐름은 다음과 같다:
첫 요청
클라이언트 → 캐시 확인 → 없음(MISS) → 서버 요청 → 응답 저장
다음 요청
클라이언트 → 캐시 확인 → 있음(HIT) → 서버 요청 없이 사용
실무에서는 주로 GET, HEAD 처럼 조회 목적이고 결과 재사용이 비교적 안전한 요청에 캐시를 사용한다.
Chrome에서도 확인할 수 있다.
F12 → Network 탭 → Size 열 확인
memory cache, disk cache가 보이면 캐시에서 가져온 것이다.
캐시 없이 새로 받고 싶다면 Shift + F5로 강력 새로고침을 할 수 있다.

| Method | 안전성 | 멱등성 | 캐시 사용 |
|---|---|---|---|
| GET | O | O | 주로 사용 |
| HEAD | O | O | 주로 사용 |
| POST | X | X | 가능은 하나 일반적으로 잘 사용하지 않음 |
| PUT | X | O | 거의 사용하지 않음 |
| PATCH | X | 보통 X | 거의 사용하지 않음 |
| DELETE | X | O | 거의 사용하지 않음 |
이 표에서 가장 중요한 비교는 GET과 POST이다.
GET
= 조회 목적
= 서버 데이터를 바꾸지 않음
= 여러 번 요청해도 최종 결과가 같음
POST
= 생성 또는 처리 목적
= 서버 데이터를 바꿀 수 있음
= 여러 번 요청하면 중복 생성 위험이 있음
Method 속성은 이론이 아니라, 장애 상황에서 재시도와 중복 처리를 판단하는 실무 기준이다.

상태 코드는 서버가 클라이언트에게 요청 처리 결과를 알려주는 숫자이다.
HTTP 상태 코드는 요청이 성공했는지, 실패했는지, 실패했다면 누구의 문제인지 알려준다.
상태 코드를 모두 외울 필요는 없다.
먼저 100번대 단위의 의미를 잡는 것이 중요하다.
| 상태 코드 | 의미 | 중요도 |
|---|---|---|
| 1xx | 정보, 처리 중 | 하 |
| 2xx | 성공 | 상 |
| 3xx | 리다이렉션 | 중 |
| 4xx | 클라이언트 오류 | 상 |
| 5xx | 서버 오류 | 상 |
| 코드 | 의미 | 예시 |
|---|---|---|
| 200 OK | 요청 성공 | 회원 조회 성공 |
| 201 Created | 새로운 리소스 생성 | 회원가입, 게시글 작성 |
| 202 Accepted | 요청은 받았지만 처리 완료 전 | 랭킹 집계, 배치 작업 |
| 204 No Content | 성공했지만 응답 Body 없음 | 삭제 성공 |
예를 들어 게시글 생성에 성공했다면 201 Created가 적절하다.
POST /posts
→ 새 게시글 생성
→ 201 Created
랭킹 집계처럼 오래 걸리는 작업은 요청을 받자마자 결과를 줄 수 없을 수 있다.
이때는 202 Accepted를 사용할 수 있다.
요청은 받았다.
하지만 아직 처리는 끝나지 않았다.
DELETE가 성공했지만 돌려줄 데이터가 없다면 204 No Content가 어울린다.
3xx는 다른 위치로 이동이 필요한 상태이다.
/a 요청
→ 서버가 /b로 가라고 응답
→ 클라이언트가 /b로 이동
이것이 리다이렉션이다.
OAuth에서도 리다이렉션을 볼 수 있다.
내 서비스에서 카카오 로그인 클릭
→ 카카오 로그인 페이지로 이동
→ 카카오에서 로그인
→ 다시 내 서비스로 돌아옴
이 “갔다가 돌아오는 과정”에서 3xx 리다이렉션이 사용된다.
4xx는 클라이언트 요청이 잘못된 경우이다.
| 코드 | 의미 | 예시 |
|---|---|---|
| 400 Bad Request | 요청 형식이 잘못됨 | 숫자 자리에 문자를 보냄 |
| 401 Unauthorized | 인증 필요 | 로그인하지 않음 |
| 403 Forbidden | 권한 없음 | 일반 사용자가 관리자 API 호출 |
| 404 Not Found | 리소스 없음 | 없는 게시글 조회 |
특히 401과 403은 구분해야 한다.
| 코드 | 질문으로 이해하기 | 의미 |
|---|---|---|
| 401 | “너 누구야?” | 로그인이 필요함 |
| 403 | “누군지는 알겠는데 권한 없어.” | 권한이 부족함 |
예를 들어 로그인 하지 않은 사용자가 마이페이지에 접근하면 401이 어울린다.
로그인은 했지만 일반 사용자가 관리자 페이지에 접근하면 403이 어울린다.
5xx는 클라이언트 요청은 정상인데 서버가 처리하지 못한 경우이다.
| 코드 | 의미 |
|---|---|
| 500 Internal Server Error | 서버 내부 오류 |
| 503 Service Unavailable | 서비스 이용 불가 |
503은 내 단계에서는 자주 못 볼 수 있다.
하지만 운영 환경에서는 서버 점검, 장애, 과부하 상황에서 충분히 발생할 수 있다.
중요한 것은 클라이언트가 문제를 500으로 처리하지 않는 것이다.
| 상황 | 적절한 상태 코드 |
|---|---|
| 요청 데이터가 잘못됨 | 400 |
| 로그인이 필요함 | 401 |
| 권한이 없음 | 403 |
| 요청한 리소스가 없음 | 404 |
| 서버 내부 예외 발생 | 500 |
| 서비스 일시 중단 | 503 |
상태 코드는 “누가 무엇을 고쳐야 하는지” 알려주는 신호이다.
인터페이스이다.API는 클라이언트와 서버가 정해진 방식으로 통신하기 위한 약속이다.
API는 Application Programming Interface이다.
여기서 중요한 단어는 Interface이다.
Java에서 인터페이스를 배울 때 핵심은 “구현체를 몰라도 정해진 기능을 사용할 수 있다”는 점이었다.
예를 들어 결제 기능이 있다고 하자.
public interface Payment {
void pay(int amount);
}
사용하는 입장에서는 pay()라는 약속만 알면 된다.
실제 구현체가 카드 결제인지, 카카오페이 결제인지, 계좌이체인지는 몰라도 된다.
API도 마찬가지다.
클라이언트는 서버가 Spring으로 만들어졌는지, Java로 만들어졌는지, JavaScript로 만들어졌는지 몰라도 된다.
중요한 것은 API 명세이다.
어떤 URL로
어떤 Method로
어떤 데이터를 보내면
어떤 응답을 받을 수 있는가
이 약속만 지키면 클라이언트와 서버는 통신할 수 있다.
API는 자판기 버튼과 비슷하다.
사용자는 자판기 내부의 모터 구조나 냉각 장치를 몰라도 된다.
콜라 버튼을 누른다.
돈을 넣는다.
콜라가 나온다.
내부 구현을 몰라도 정해진 사용 방법만 알면 된다.
API도 같다.
클라이언트는 API 명세대로 요청한다.
서버는 API 명세대로 응답한다.
API는 내부 구현을 숨기고, 외부에는 정해진 사용 방법만 제공하는 인터페이스이다.
API 응답을 확인하다 보면 JSON 데이터가 한 줄로 길게 나올 수 있다.
{"name":"kim","age":20,"address":"seoul"}
Pretty Print는 이 데이터를 사람이 읽기 좋게 줄바꿈과 들여쓰기로 정리해준다.
{
"name": "kim",
"age": 20,
"address": "seoul"
}
중요한 점은 데이터 내용이 바뀌지 않는다는 것이다.
Pretty Print
= 데이터 변경 X
= 보기 좋게 정렬 O
Pretty Print는 API 응답을 읽기 좋게 보여주는 기능이다.
RESTful API는 URL만 봐도 API의 역할을 예측할 수 있게 만드는 설계 방식이다.
API 주소를 아무렇게나 만들면 협업이 어려워진다.
예를 들어 회원 목록 조회 API가 다음처럼 되어 있다고 하자.
GET /getMemberListNow
의미는 알 수 있지만 깔끔하지 않다.
URL에 동작이 직접 들어가 있고, 일관성도 떨어진다.
RESTful 하게 표현하면 다음과 같다.
GET /members
이렇게 작성하면 URL만 봐도 회원 목록을 조회하는 API라는 것을 예상할 수 있다.
| 원칙 | 나쁜 예 | 좋은 예 |
|---|---|---|
| 동사보다 명사, 단수보다 복수 | /user/getItem | /users/items |
마지막 / 제거 | /users/ | /users |
_ 대신 -, 소문자 사용 | /user_list | /user-list |
| 확장자 제거 | /image.png | /images |
| 계층 관계 표현 | /items/{memberId}/members/{itemId} | /members/{memberId}/items/{itemId} |
RESTful 설계에서는 URL에 행동을 직접 쓰기보다 Method로 행동을 표현한다.
URL = 자원
Method = 행동
예를 들어 게시글 API는 다음처럼 설계할 수 있다.
| 기능 | Method | URL |
|---|---|---|
| 게시글 목록 조회 | GET | /posts |
| 게시글 단건 조회 | GET | /posts/{id} |
| 게시글 생성 | POST | /posts |
| 게시글 전체 수정 | PUT | /posts/{id} |
| 게시글 일부 수정 | PATCH | /posts/{id} |
| 게시글 삭제 | DELETE | /posts/{id} |
같은 URL이라도 Method가 다르면 의미가 달라진다.
GET /posts/1
= 1번 게시글 조회
DELETE /posts/1
= 1번 게시글 삭제
RESTful API는 URL은 자원으로, 행동은 Method로 표현하는 설계 방식이다.
Spring Controller는 HTTP 요청을 Java 메서드에 연결한다.
HTTP 개념은 Spring 코드에서 바로 나타난다.
예를 들어 클라이언트가 다음 요청을 보낸다고 하자.
GET /members/1
Spring에서는 다음처럼 작성할 수 있다.
@GetMapping("/members/{id}")
public ResponseEntity<Member> getMember(@PathVariable Long id) {
Member member = memberService.findById(id);
return ResponseEntity.ok(member);
}
이 코드는 다음처럼 연결된다.
| HTTP 개념 | Spring 코드 |
|---|---|
| GET Method | @GetMapping |
URL /members/{id} | "/members/{id}" |
| URL의 id 값 | @PathVariable Long id |
| 200 OK | ResponseEntity.ok(member) |
| 응답 Body | member |
즉, @GetMapping은 단순한 어노테이션이 아니다.
GET 요청이
/members/{id} 주소로 들어오면
이 Java 메서드가 처리한다.
회원 생성 요청은 다음처럼 표현할 수 있다.
POST /members
Spring에서는 다음처럼 작성할 수 있다.
@PostMapping("/members")
public ResponseEntity<Member> createMember(@RequestBody MemberDto dto) {
Member newMember = memberService.create(dto);
return ResponseEntity.status(201).body(newMember);
}
이 코드는 다음 의미를 가진다.
| HTTP 개념 | Spring 코드 |
|---|---|
| POST Method | @PostMapping |
| 요청 Body | @RequestBody MemberDto dto |
| 리소스 생성 성공 | ResponseEntity.status(201) |
| 응답 Body | newMember |
RequestBody는 HTTP 요청 Body에 들어온 JSON 데이터를 Java 객체로 받기 위한 도구이다.
ResponseEntity.status(201).body(newMember)는 새 리소스가 생성되었으므로 201 상태 코드와 생성된 데이터를 함께 응답하겠다는 의미이다.

GET /members/1 요청이 들어왔을 때의 흐름은 다음과 같다.
1. 클라이언트가 GET /members/1 요청을 보낸다.
2. Spring이 요청 Method와 URL을 확인한다.
3. @GetMapping("/members/{id}")와 매칭한다.
4. URL의 1을 @PathVariable Long id에 넣는다.
5. getMember(1) 메서드를 실행한다.
6. Service에서 회원 데이터를 조회한다.
7. ResponseEntity.ok(member)를 만든다.
8. HTTP 응답으로 200 OK와 회원 데이터를 반환한다.
이 흐름을 이해하면 Spring Controller 코드는 단순 문법이 아니라 HTTP 요청과 응답을 처리하는 연결 지점으로 보인다.
헷갈린 개념들도 결국 HTTP 요청과 응답 흐름 안에 있다.
| 개념 | 처음 헷갈린 지점 | 정리 |
|---|---|---|
| Empty Line | 그냥 빈 줄 아닌가? | Header와 Body를 구분하는 필수 구분선 |
| Cache | 단순 저장소인가? | 이전 응답을 저장해 서버 요청을 줄이는 장치 |
| OAuth | 지금 다 알아야 하나? | 현재는 외부 로그인과 리다이렉션 정도만 이해 |
| Pretty Print | 데이터가 바뀌는 건가? | 데이터는 그대로, 보기 좋게 정렬만 함 |
| 503 | 거의 안 쓰는 코드인가? | 운영 환경에서는 장애·점검·과부하 때 발생 가능 |
| 개념 | 중요도 | 기억할 내용 |
|---|---|---|
| HTTP Request / Response | 상 | 클라이언트는 요청하고 서버는 응답한다 |
| Stateless | 상 | HTTP는 기본적으로 이전 상태를 기억하지 않는다 |
| HTTP Method | 상 | Method는 요청의 의도를 나타낸다 |
| GET / POST / PUT / PATCH / DELETE | 상 | 조회, 생성, 전체 수정, 일부 수정, 삭제 |
| 안전성 | 중상 | 서버 데이터를 바꾸지 않는 성질 |
| 멱등성 | 상 | 여러 번 요청해도 최종 결과가 같은 성질 |
| 캐시가능성 | 중 | 응답을 저장해 재사용할 수 있는 성질 |
| Status Code | 상 | 요청 처리 결과를 숫자로 알려준다 |
| API | 상 | 클라이언트와 서버가 통신하는 인터페이스 |
| RESTful API | 중상 | URL은 자원, Method는 행동을 표현한다 |
| Spring Controller | 상 | HTTP 요청을 Java 메서드에 연결한다 |
HTTP는 클라이언트와 서버가 요청과 응답을 주고받기 위한 통신 규칙이다. 클라이언트는 Method와 URL로 요청의 의도를 표현하고, 서버는 상태 코드와 Body로 처리 결과를 응답한다.
API는 이 HTTP 흐름 위에서 클라이언트와 서버가 어떤 방식으로 통신할지 정한 인터페이스이다. 클라이언트는 서버 내부 구현을 몰라도 API 명세만 알면 요청을 보내고 응답을 받을 수 있다.
RESTful API는 API를 더 예측 가능하고 읽기 쉽게 만드는 설계 방식이다. URL은 자원을 표현하고, 행동은 HTTP Method로 표현한다.
처음에는 캐시, OAuth, Pretty Print, Empty Line 같은 개념이 따로 떨어져 보였다. 하지만 정리해보면 모두 HTTP 요청과 응답 흐름 안에 있는 개념이었다. 캐시는 응답을 재사용해 서버 요청을 줄이는 장치이고, OAuth는 외부 로그인 과정에서 리다이렉션이 사용되는 예시이며, Pretty Print는 API 응답을 읽기 좋게 보여주는 기능이다. Empty Line은 Header와 Body를 구분하는 HTTP 메시지의 필수 구조이다.
결국 Spring Controller 코드는 HTTP를 Java 코드로 표현한 것이다. @GetMapping, @PostMapping, @RequestBody, @PathVariable, ResponseEntity는 각각 HTTP Method, URL, 요청 Body, 경로 변수, 응답 상태 코드를 다루기 위한 도구이다.
앞으로 Spring을 공부할 때는 코드를 외우기보다 “클라이언트가 어떤 요청을 보냈고, 서버가 어떤 응답을 내려주는가”라는 흐름을 기준으로 이해해야 한다.