본문으로 건너뛰기

📬 HTTP 메서드

📖 정의

HTTP 메서드는 클라이언트가 서버에게 수행하고자 하는 동작을 나타냅니다. 각 메서드는 특정한 의미와 용도를 가지고 있으며, RESTful API 설계의 핵심입니다.

🎯 비유로 이해하기

도서관 시스템

GET    = 책 찾아보기 (조회)
├─ 사서에게 "이 책 있나요?" 물어보기
├─ 데이터를 가져오기만 함
└─ 도서관 상태 변화 없음

POST = 신간 등록하기 (생성)
├─ 새로운 책을 도서관에 추가
├─ 도서관 상태 변화
└─ 새로운 리소스 생성

PUT = 책 정보 전체 수정 (전체 업데이트)
├─ 책의 모든 정보를 새로 작성
└─ 전체 교체

PATCH = 책 정보 부분 수정 (부분 업데이트)
├─ 책의 일부 정보만 수정 (예: 대출 가능 여부)
└─ 일부만 변경

DELETE = 책 폐기하기 (삭제)
├─ 도서관에서 책 제거
└─ 리소스 삭제

💡 주요 HTTP 메서드

GET - 데이터 조회

용도: 서버에서 리소스를 가져옴

특징:
├─ 안전(Safe): 서버 상태를 변경하지 않음
├─ 멱등(Idempotent): 여러 번 호출해도 결과 동일
├─ 캐시 가능
└─ 브라우저 히스토리에 남음

요청 예시:
GET /api/users HTTP/1.1
Host: example.com
// JavaScript fetch API
fetch('https://api.example.com/users')
.then(response => response.json())
.then(data => console.log(data));

// jQuery
$.get('https://api.example.com/users', function(data) {
console.log(data);
});

// axios
axios.get('https://api.example.com/users')
.then(response => console.log(response.data));
# curl
curl https://api.example.com/users

# 특정 사용자 조회
curl https://api.example.com/users/123

# 쿼리 파라미터 사용
curl "https://api.example.com/users?page=1&limit=10"

응답 예시:

HTTP/1.1 200 OK
Content-Type: application/json

{
"users": [
{ "id": 1, "name": "홍길동" },
{ "id": 2, "name": "김철수" }
]
}

POST - 데이터 생성

용도: 서버에 새로운 리소스 생성

특징:
├─ 안전하지 않음: 서버 상태 변경
├─ 멱등하지 않음: 여러 번 호출 시 여러 리소스 생성
├─ 캐시 불가능
└─ 요청 바디에 데이터 포함

요청 예시:
POST /api/users HTTP/1.1
Host: example.com
Content-Type: application/json

{
"name": "홍길동",
"email": "hong@example.com"
}
// fetch API
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: '홍길동',
email: 'hong@example.com'
})
})
.then(response => response.json())
.then(data => console.log(data));

// axios
axios.post('https://api.example.com/users', {
name: '홍길동',
email: 'hong@example.com'
})
.then(response => console.log(response.data));
# curl
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"name":"홍길동","email":"hong@example.com"}'

응답 예시:

HTTP/1.1 201 Created
Location: /api/users/123
Content-Type: application/json

{
"id": 123,
"name": "홍길동",
"email": "hong@example.com",
"createdAt": "2025-01-26T10:00:00Z"
}

PUT - 전체 데이터 수정

용도: 리소스 전체를 새로운 데이터로 교체

특징:
├─ 안전하지 않음: 서버 상태 변경
├─ 멱등함: 여러 번 호출해도 결과 동일
├─ 리소스 전체를 교체
└─ 존재하지 않으면 생성할 수도 있음

요청 예시:
PUT /api/users/123 HTTP/1.1
Host: example.com
Content-Type: application/json

{
"name": "홍길동",
"email": "newemail@example.com",
"phone": "010-1234-5678"
}
// fetch API
fetch('https://api.example.com/users/123', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: '홍길동',
email: 'newemail@example.com',
phone: '010-1234-5678'
})
})
.then(response => response.json())
.then(data => console.log(data));

// axios
axios.put('https://api.example.com/users/123', {
name: '홍길동',
email: 'newemail@example.com',
phone: '010-1234-5678'
})
.then(response => console.log(response.data));
# curl
curl -X PUT https://api.example.com/users/123 \
-H "Content-Type: application/json" \
-d '{"name":"홍길동","email":"newemail@example.com","phone":"010-1234-5678"}'

PATCH - 부분 데이터 수정

용도: 리소스의 일부만 수정

특징:
├─ 안전하지 않음: 서버 상태 변경
├─ 멱등할 수도, 아닐 수도 있음
├─ 일부 필드만 수정
└─ PUT보다 효율적

요청 예시:
PATCH /api/users/123 HTTP/1.1
Host: example.com
Content-Type: application/json

{
"email": "newemail@example.com"
}
// fetch API
fetch('https://api.example.com/users/123', {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'newemail@example.com'
})
})
.then(response => response.json())
.then(data => console.log(data));

// axios
axios.patch('https://api.example.com/users/123', {
email: 'newemail@example.com'
})
.then(response => console.log(response.data));
# curl
curl -X PATCH https://api.example.com/users/123 \
-H "Content-Type: application/json" \
-d '{"email":"newemail@example.com"}'

PUT vs PATCH 비교:

기존 데이터:
{
"id": 123,
"name": "홍길동",
"email": "old@example.com",
"phone": "010-1111-2222"
}

PUT 요청 (전체 교체):
{
"name": "홍길동",
"email": "new@example.com"
}
결과:
{
"id": 123,
"name": "홍길동",
"email": "new@example.com"
// phone 필드 사라짐!
}

PATCH 요청 (부분 수정):
{
"email": "new@example.com"
}
결과:
{
"id": 123,
"name": "홍길동",
"email": "new@example.com",
"phone": "010-1111-2222"
// phone 필드 유지됨!
}

DELETE - 데이터 삭제

용도: 리소스 삭제

특징:
├─ 안전하지 않음: 서버 상태 변경
├─ 멱등함: 여러 번 삭제해도 결과 동일
├─ 응답 바디 없을 수도 있음
└─ 복구 불가능할 수 있음

요청 예시:
DELETE /api/users/123 HTTP/1.1
Host: example.com
// fetch API
fetch('https://api.example.com/users/123', {
method: 'DELETE'
})
.then(response => {
if (response.ok) {
console.log('삭제 완료');
}
});

// axios
axios.delete('https://api.example.com/users/123')
.then(response => console.log('삭제 완료'));
# curl
curl -X DELETE https://api.example.com/users/123

응답 예시:

HTTP/1.1 204 No Content

또는

HTTP/1.1 200 OK
Content-Type: application/json

{
"message": "사용자가 삭제되었습니다",
"deletedId": 123
}

🔍 기타 HTTP 메서드

HEAD - 헤더만 조회

용도: GET과 동일하지만 바디 없이 헤더만 반환

사용 사례:
├─ 리소스 존재 여부 확인
├─ 파일 크기 확인
└─ 수정 시간 확인

예시:
HEAD /api/users/123 HTTP/1.1
Host: example.com

응답:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 256
Last-Modified: Sat, 26 Jan 2025 10:00:00 GMT
# curl
curl -I https://api.example.com/users/123

OPTIONS - 지원 메서드 확인

용도: 서버가 지원하는 메서드 확인 (CORS 사전 요청에 사용)

예시:
OPTIONS /api/users HTTP/1.1
Host: example.com

응답:
HTTP/1.1 200 OK
Allow: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Origin: *
# curl
curl -X OPTIONS https://api.example.com/users -i

📊 메서드 속성 비교

┌─────────┬──────┬────────┬─────────┬────────────┐
│ 메서드 │ 안전 │ 멱등성 │ 캐시 가능│ 요청 바디 │
├─────────┼──────┼────────┼─────────┼────────────┤
│ GET │ ✅ │ ✅ │ ✅ │ ❌ │
│ POST │ ❌ │ ❌ │ ❌ │ ✅ │
│ PUT │ ❌ │ ✅ │ ❌ │ ✅ │
│ PATCH │ ❌ │ △ │ ❌ │ ✅ │
│ DELETE │ ❌ │ ✅ │ ❌ │ △ │
│ HEAD │ ✅ │ ✅ │ ✅ │ ❌ │
│ OPTIONS │ ✅ │ ✅ │ ❌ │ ❌ │
└─────────┴──────┴────────┴─────────┴────────────┘

용어 설명:
- 안전(Safe): 서버 상태를 변경하지 않음
- 멱등(Idempotent): 여러 번 수행해도 결과가 동일
- 캐시 가능: 응답을 캐시할 수 있음

💡 RESTful API 설계

CRUD와 HTTP 메서드 매핑

Create  → POST
Read → GET
Update → PUT / PATCH
Delete → DELETE

RESTful API 예시

리소스: 사용자(users)

GET /users - 모든 사용자 조회
GET /users/123 - 특정 사용자 조회
POST /users - 새 사용자 생성
PUT /users/123 - 사용자 전체 수정
PATCH /users/123 - 사용자 부분 수정
DELETE /users/123 - 사용자 삭제

중첩 리소스:
GET /users/123/posts - 특정 사용자의 게시글 목록
GET /users/123/posts/456 - 특정 게시글 조회
POST /users/123/posts - 새 게시글 작성
PUT /users/123/posts/456 - 게시글 수정
DELETE /users/123/posts/456 - 게시글 삭제

실전 예시: 블로그 API

// 블로그 게시글 API

// 1. 게시글 목록 조회 (페이징, 필터링)
GET /api/posts?page=1&limit=10&category=tech

// 2. 특정 게시글 조회
GET /api/posts/123

// 3. 새 게시글 작성
POST /api/posts
{
"title": "HTTP 메서드 완전 정복",
"content": "...",
"category": "tech"
}

// 4. 게시글 전체 수정
PUT /api/posts/123
{
"title": "HTTP 메서드 완전 정복 (수정됨)",
"content": "...",
"category": "tech"
}

// 5. 게시글 부분 수정 (조회수 증가)
PATCH /api/posts/123
{
"views": 101
}

// 6. 게시글 삭제
DELETE /api/posts/123

// 7. 댓글 관련
GET /api/posts/123/comments - 댓글 목록
POST /api/posts/123/comments - 댓글 작성
DELETE /api/posts/123/comments/456 - 댓글 삭제

🤔 자주 묻는 질문

Q1. POST vs PUT 차이점은?

A:

POST:
├─ 새로운 리소스 생성
├─ 서버가 리소스 URI 결정
├─ 멱등하지 않음 (여러 번 호출 시 여러 리소스 생성)
└─ 예: POST /users

PUT:
├─ 리소스 전체 교체
├─ 클라이언트가 리소스 URI 지정
├─ 멱등함 (여러 번 호출해도 결과 동일)
└─ 예: PUT /users/123

실전 비유:
POST = "새 계좌 만들어주세요" (은행이 계좌번호 부여)
PUT = "123번 계좌 정보를 이걸로 바꿔주세요"

Q2. GET에 바디를 보낼 수 있나?

A:

기술적으로는 가능하지만:
├─ HTTP 스펙상 허용되지만 권장하지 않음
├─ 많은 서버/프레임워크가 무시함
├─ 캐싱, 로깅 등에서 예상치 못한 동작
└─ 쿼리 파라미터 사용 권장

❌ 나쁜 예:
GET /api/users
{
"filters": { "age": 25 }
}

✅ 좋은 예:
GET /api/users?age=25

또는 복잡한 검색은 POST 사용:
POST /api/users/search
{
"filters": { "age": 25, "city": "서울" }
}

Q3. DELETE에 바디를 보낼 수 있나?

A:

가능하지만 조심해야 함:
├─ HTTP 스펙상 허용
├─ 일부 서버/프록시가 무시할 수 있음
├─ 보통 URI에 리소스 ID 포함
└─ 바디는 추가 정보 전달용

사용 예시:

✅ 일반적인 방법:
DELETE /api/users/123

✅ 복잡한 삭제 조건:
DELETE /api/posts/bulk
{
"ids": [1, 2, 3, 4, 5]
}

✅ 삭제 사유 전달:
DELETE /api/users/123
{
"reason": "사용자 요청",
"confirm": true
}

Q4. 멱등성이 왜 중요한가?

A:

멱등성의 중요성:

1. 네트워크 실패 대응
├─ 요청 실패 시 재시도 가능
├─ 중복 요청 걱정 없음
└─ 안전한 재처리

예시:
GET /users/123 → 여러 번 조회해도 안전
POST /users → 여러 번 호출 시 여러 사용자 생성!
PUT /users/123 → 여러 번 호출해도 같은 결과
DELETE /users/123 → 여러 번 삭제해도 같은 결과

2. 클라이언트 재시도 로직
├─ 타임아웃 시 안전하게 재시도
└─ 자동 재시도 메커니즘 구현 가능

3. 캐싱과 최적화
├─ 멱등한 요청은 캐싱 가능
└─ 성능 최적화 용이

Q5. 실무에서 메서드 선택 기준은?

A:

선택 가이드:

1. 데이터 조회만 한다면
→ GET

2. 새로운 데이터를 만든다면
→ POST

3. 데이터를 완전히 교체한다면
→ PUT
예: 사용자 프로필 전체 업데이트

4. 데이터 일부만 수정한다면
→ PATCH
예: 좋아요 수 증가, 상태만 변경

5. 데이터를 삭제한다면
→ DELETE

6. 복잡한 조회/검색이라면
→ POST /search
(GET의 URL 길이 제한 회피)

실전 예시:

// ✅ 좋은 예
GET /api/products - 상품 목록
GET /api/products/123 - 상품 상세
POST /api/products - 상품 등록
PATCH /api/products/123/stock - 재고만 수정
PUT /api/products/123 - 상품 정보 전체 수정
DELETE /api/products/123 - 상품 삭제

// ❌ 나쁜 예
GET /api/deleteProduct?id=123 - 삭제는 DELETE 사용
POST /api/getProducts - 조회는 GET 사용
POST /api/updateProduct - 수정은 PUT/PATCH 사용

🎓 실습하기

1. fetch API로 연습

// GET 요청
async function getUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`);
const data = await response.json();
return data;
}

// POST 요청
async function createUser(userData) {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData)
});
return await response.json();
}

// PUT 요청
async function updateUser(id, userData) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData)
});
return await response.json();
}

// PATCH 요청
async function patchUser(id, partialData) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(partialData)
});
return await response.json();
}

// DELETE 요청
async function deleteUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: 'DELETE'
});
return response.ok;
}

2. 에러 처리 포함

async function apiRequest(url, options = {}) {
try {
const response = await fetch(url, options);

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

return await response.json();
} catch (error) {
console.error('API 요청 실패:', error);
throw error;
}
}

// 사용 예시
try {
const user = await apiRequest('https://api.example.com/users/123');
console.log(user);
} catch (error) {
console.error('사용자 조회 실패');
}

🔗 관련 문서

🎬 마치며

HTTP 메서드는 RESTful API의 핵심입니다. 각 메서드의 특성을 이해하고 적절히 사용하면 직관적이고 유지보수하기 쉬운 API를 설계할 수 있습니다!

다음 단계: HTTP 상태 코드를 읽어보며 200, 404, 500 등의 상태 코드를 알아보세요.