PUT vs POST: 어떤 점이 다를까요? (2026)
실제 예제를 통한 HTTP PUT 및 POST 메서드에 대한 명확한 설명
Hypereal로 구축 시작하기
단일 API를 통해 Kling, Flux, Sora, Veo 등에 액세스하세요. 무료 크레딧으로 시작하고 수백만으로 확장하세요.
신용카드 불필요 • 10만 명 이상의 개발자 • 엔터프라이즈 지원
PUT vs POST: 차이점은 무엇인가요? (2026)
HTTP PUT과 POST의 차이점은 웹 개발에서 가장 흔히 오해받는 개념 중 하나입니다. 두 메서드 모두 서버로 데이터를 전송하지만, 근본적으로 다른 의미론(semantics), 동작 및 사용 사례를 가지고 있습니다. 올바른 REST API를 구축하기 위해서는 각 메서드를 언제 사용해야 하는지 이해하는 것이 필수적입니다.
이 가이드에서는 실질적인 예시, 비교 표, 그리고 실제 시나리오를 통해 그 차이점을 명확하게 설명합니다.
핵심 차이점
PUT은 특정 URL에 있는 리소스를 대체합니다. 리소스가 존재하면 전체가 교체되고, 존재하지 않으면 새로 생성될 수 있습니다.
POST는 처리를 위해 데이터를 리소스에 제출합니다. 데이터로 무엇을 할지는 서버가 결정하며, 일반적으로 새로운 리소스를 생성하는 데 사용됩니다.
PUT /api/users/123 → 사용자 123의 데이터를 제공된 데이터로 대체
POST /api/users → 새 사용자 생성 (서버가 ID를 할당)
이렇게 생각하면 쉽습니다:
- PUT = "이 데이터를 정확히 이 위치에 두어라"
- POST = "이 데이터를 적절하게 처리해라"
한눈에 보는 비교
| 특성 | PUT | POST |
|---|---|---|
| 목적 | 리소스 대체/업데이트 | 새 리소스 생성 또는 처리 트리거 |
| URL 대상 | 특정 리소스 (/users/123) |
컬렉션 또는 엔드포인트 (/users) |
| 멱등성(Idempotent) | 예 | 아니요 |
| 요청 본문(Body) | 전체 리소스 표현 | 부분 데이터 또는 페이로드 |
| 응답 코드 (성공) | 200 OK 또는 204 No Content | 201 Created |
| 안전성(Safe) | 아니요 | 아니요 |
| 캐시 가능 여부 | 아니요 | 드물게 가능 |
| 클라이언트의 URL 인지 | 예 (정확한 리소스 지정) | 아니요 (서버가 URL 결정) |
멱등성(Idempotency): 가장 중요한 차이점
가장 중요한 기술적 차이점은 멱등성입니다. 연산을 여러 번 수행해도 한 번 수행한 것과 결과가 같다면 그 연산은 멱등적이라고 합니다.
PUT은 멱등적입니다
동일한 PUT 요청을 10번 보내는 것은 한 번 보내는 것과 결과가 같습니다.
# 세 번의 요청 모두 정확히 같은 결과를 생성합니다.
PUT /api/users/123 {"name": "Alice", "email": "alice@example.com"}
PUT /api/users/123 {"name": "Alice", "email": "alice@example.com"}
PUT /api/users/123 {"name": "Alice", "email": "alice@example.com"}
# 결과적으로 사용자 123은 {"name": "Alice", "email": "alice@example.com"} 상태가 됩니다.
POST는 멱등적이지 않습니다
동일한 POST 요청을 3번 보내면 3개의 서로 다른 리소스가 생성됩니다.
# 각 요청은 새로운 사용자를 생성합니다.
POST /api/users {"name": "Alice", "email": "alice@example.com"} → 사용자 101 생성
POST /api/users {"name": "Alice", "email": "alice@example.com"} → 사용자 102 생성
POST /api/users {"name": "Alice", "email": "alice@example.com"} → 사용자 103 생성
# 이제 3명의 중복된 사용자가 존재하게 됩니다.
이러한 구분은 실제 애플리케이션에서 중요합니다. 네트워크 오류가 발생하여 클라이언트가 요청을 재시도할 때, PUT 재시도는 안전하지만(결과가 동일), POST 재시도는 중복 데이터를 생성할 수 있습니다.
코드 예시
Express.js REST API
const express = require('express');
const app = express();
app.use(express.json());
const users = new Map();
let nextId = 1;
// POST - 새 사용자 생성
// 클라이언트는 ID를 지정하지 않음
app.post('/api/users', (req, res) => {
const { name, email } = req.body;
// 서버가 ID 할당
const id = nextId++;
const user = { id, name, email, createdAt: new Date() };
users.set(id, user);
// 201 Created와 함께 새 리소스를 가리키는 Location 헤더 반환
res.status(201)
.location(`/api/users/${id}`)
.json(user);
});
// PUT - 기존 사용자 대체
// 클라이언트가 정확한 리소스 URL을 지정함
app.put('/api/users/:id', (req, res) => {
const id = parseInt(req.params.id);
const { name, email } = req.body;
// 전체 대체 - 모든 필드가 제공되어야 함
const user = { id, name, email, updatedAt: new Date() };
users.set(id, user);
if (users.has(id)) {
// 기존 리소스 업데이트 시 200 OK
res.status(200).json(user);
} else {
// 리소스가 새로 생성된 경우 201 Created
res.status(201).json(user);
}
});
app.listen(3000);
Python (Flask) REST API
from flask import Flask, request, jsonify
from datetime import datetime
app = Flask(__name__)
users = {}
next_id = 1
# POST - 새 사용자 생성
@app.route('/api/users', methods=['POST'])
def create_user():
global next_id
data = request.get_json()
user = {
'id': next_id,
'name': data['name'],
'email': data['email'],
'created_at': datetime.now().isoformat()
}
users[next_id] = user
next_id += 1
return jsonify(user), 201, {'Location': f'/api/users/{user["id"]}'}
# PUT - 사용자 대체
@app.route('/api/users/<int:user_id>', methods=['PUT'])
def replace_user(user_id):
data = request.get_json()
# PUT은 전체 리소스 표현을 요구함
user = {
'id': user_id,
'name': data['name'],
'email': data['email'],
'updated_at': datetime.now().isoformat()
}
status_code = 200 if user_id in users else 201
users[user_id] = user
return jsonify(user), status_code
Fetch / Axios 호출 예시
// POST - 새 사용자 생성
const newUser = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Alice',
email: 'alice@example.com'
})
});
// 응답: 201 Created, { id: 1, name: "Alice", ... }
// PUT - 사용자 1 업데이트
const updatedUser = await fetch('/api/users/1', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Alice Smith', // 업데이트된 이름
email: 'alice@example.com' // 모든 필드를 포함해야 함
})
});
// 응답: 200 OK, { id: 1, name: "Alice Smith", ... }
// Axios 사용 시
import axios from 'axios';
// POST
const { data: created } = await axios.post('/api/users', {
name: 'Bob',
email: 'bob@example.com'
});
// PUT
const { data: updated } = await axios.put(`/api/users/${created.id}`, {
name: 'Bob Johnson',
email: 'bob.johnson@example.com'
});
PUT vs POST vs PATCH
PATCH는 종종 PUT과 혼동됩니다. 세 가지의 차이점은 다음과 같습니다.
| 메서드 | 목적 | 본문 포함 내용 | 멱등성 |
|---|---|---|---|
| POST | 새 리소스 생성 | 새 리소스를 위한 데이터 | 아니요 |
| PUT | 전체 리소스 대체 | 전체 리소스 데이터 | 예 |
| PATCH | 리소스 부분 업데이트 | 변경된 필드만 | 보장되지 않음 |
// 현재 사용자 상태: { id: 1, name: "Alice", email: "alice@example.com", role: "user" }
// PUT - 모든 필드를 보내야 함 (전체 리소스 대체)
PUT /api/users/1
{
"name": "Alice Smith",
"email": "alice@example.com",
"role": "user"
}
// 만약 "role"을 생략하면, 해당 필드는 제거됩니다!
// PATCH - 변경된 부분만 전송
PATCH /api/users/1
{
"name": "Alice Smith"
}
// "name"만 변경됩니다. "email"과 "role"은 그대로 유지됩니다.
자주 하는 실수 중 하나는 이 점입니다: PUT은 리소스 전체를 대체합니다. PUT 요청 시 특정 필드를 누락하면 해당 필드는 삭제되거나 null로 설정될 수 있습니다. 특정 필드만 업데이트하려면 PATCH를 사용하세요.
언제 PUT을 사용해야 하나요?
다음과 같은 경우에 PUT을 사용하십시오.
| 시나리오 | 예시 |
|---|---|
| 클라이언트가 리소스 URL을 제어할 때 | PUT /api/configs/theme |
| 전체 리소스를 대체할 때 | 사용자 프로필 양식 업데이트 |
| Upsert(없으면 생성, 있으면 대체) 작업 시 | 존재하지 않으면 생성, 존재하면 교체 |
| 특정 경로에 파일 업로드 시 | PUT /api/files/report-2026.pdf |
| 멱등한 업데이트가 필요할 때 | 재시도가 안전해야 하는 모든 업데이트 |
// 올바른 PUT 사용 사례
// 1. 사용자 설정 (클라이언트가 URL을 알고 있고 전체를 대체함)
app.put('/api/users/:id/settings', (req, res) => {
const settings = req.body; // 전체 설정 객체
db.settings.replace(req.params.id, settings);
res.json(settings);
});
// 2. 알려진 경로에 파일 업로드
app.put('/api/documents/:filename', (req, res) => {
storage.write(req.params.filename, req.body);
res.status(204).end();
});
// 3. 구성 정보 Upsert
app.put('/api/configs/:key', (req, res) => {
db.configs.upsert(req.params.key, req.body.value);
res.json({ key: req.params.key, value: req.body.value });
});
언제 POST를 사용해야 하나요?
다음과 같은 경우에 POST를 사용하십시오.
| 시나리오 | 예시 |
|---|---|
| 서버가 리소스 ID를 할당할 때 | 새 주문 생성 |
| 멱등적이지 않은 작업 시 | 결제 처리 |
| 복잡한 처리 시 | 검색 쿼리 실행 |
| 액션(동작)을 트리거할 때 | 이메일 전송, 작업 시작 |
| 데이터 제출 시 | 양식 제출, 파일 업로드 |
// 올바른 POST 사용 사례
// 1. 리소스 생성 (서버가 ID 할당)
app.post('/api/orders', (req, res) => {
const order = db.orders.create(req.body);
res.status(201).json(order);
});
// 2. 액션 트리거
app.post('/api/emails/send', (req, res) => {
emailService.send(req.body);
res.status(202).json({ message: '이메일이 큐에 추가됨' });
});
// 3. 결제 처리 (절대로 멱등적이지 않음!)
app.post('/api/payments', (req, res) => {
const result = paymentGateway.charge(req.body);
res.status(201).json(result);
});
// 4. URL에 담기에 너무 복잡한 쿼리
app.post('/api/search', (req, res) => {
const results = searchEngine.query(req.body);
res.json(results);
});
흔히 저지르는 실수
실수 1: 업데이트에 POST 사용
// 나쁜 예 - 업데이트에 POST 사용
app.post('/api/users/123/update', (req, res) => { ... });
// 좋은 예 - 업데이트에 PUT (또는 PATCH) 사용
app.put('/api/users/123', (req, res) => { ... });
실수 2: 전체 데이터를 보내지 않고 PUT 사용
// 나쁜 예 - PUT으로 부분 데이터 전송 (email 필드가 삭제될 수 있음!)
await axios.put('/api/users/1', { name: '새 이름' });
// 좋은 예 - PUT으로 전체 데이터 전송
await axios.put('/api/users/1', {
name: '새 이름',
email: 'existing@email.com',
role: 'user'
});
// 또는 부분 업데이트를 위해 PATCH 사용
await axios.patch('/api/users/1', { name: '새 이름' });
실수 3: POST에 멱등성을 기대함
// 나쁜 예 - 멱등성 보호 장치 없이 POST 재시도
try {
await axios.post('/api/payments', paymentData);
} catch (error) {
// 중복 결제가 발생할 수 있음!
await axios.post('/api/payments', paymentData);
}
// 좋은 예 - 멱등성 키(Idempotency-Key) 사용
await axios.post('/api/payments', paymentData, {
headers: { 'Idempotency-Key': 'unique-request-id-12345' }
});
결정 플로우차트
PUT과 POST 사이에서 결정할 때 이 가이드를 따르세요:
클라이언트가 정확한 리소스 URL을 알고 있는가?
├── 예 → 이것이 전체 대체 작업인가?
│ ├── 예 → PUT 사용
│ └── 아니요 (부분 업데이트) → PATCH 사용
└── 아니요 → 이것이 새 리소스를 생성하는 작업인가?
├── 예 → POST 사용
└── 아니요 (액션 트리거) → POST 사용
REST API 모범 사례 요약
| 작업 | 메서드 | URL 패턴 | 응답 |
|---|---|---|---|
| 목록 조회 | GET | /api/users |
200 + 배열 |
| 단일 조회 | GET | /api/users/123 |
200 + 객체 |
| 생성 | POST | /api/users |
201 + 객체 |
| 대체 | PUT | /api/users/123 |
200 + 객체 |
| 부분 업데이트 | PATCH | /api/users/123 |
200 + 객체 |
| 삭제 | DELETE | /api/users/123 |
204 No Content |
마무리하며
PUT과 POST는 서로 다른 용도로 사용됩니다. PUT은 알려진 URL의 리소스를 대체하며 멱등적입니다. 반면 POST는 새 리소스를 생성하거나 처리를 트리거하며 멱등적이지 않습니다. 클라이언트가 리소스의 위치를 제어하고 완전한 리소스를 제공할 수 있을 때는 PUT을 사용하십시오. 서버가 리소스 위치를 결정하거나 반복해서는 안 되는 부수 효과가 있는 작업에는 POST를 사용하십시오.
이미지, 비디오 또는 말하는 아바타와 같은 AI 기반 미디어 생성을 통합하는 REST API를 구축하고 있다면, Hypereal AI를 무료로 체험해 보세요 (35 크레딧 제공, 신용카드 불필요). Hypereal AI의 API는 모든 엔드포인트에 대해 명확한 PUT 및 POST 의미론을 갖춘 REST 모범 사례를 따릅니다.
