HTTP DELETE Method: 완벽 가이드 (2026)
REST API의 HTTP DELETE 메서드에 대해 알아야 할 모든 것
Hypereal로 구축 시작하기
단일 API를 통해 Kling, Flux, Sora, Veo 등에 액세스하세요. 무료 크레딧으로 시작하고 수백만으로 확장하세요.
신용카드 불필요 • 10만 명 이상의 개발자 • 엔터프라이즈 지원
HTTP DELETE 메서드: 완벽 가이드 (2026)
HTTP DELETE 메서드는 서버에서 리소스를 삭제합니다. 이는 RESTful API에서 사용되는 4가지 핵심 HTTP 메서드(GET, POST, PUT과 함께) 중 하나이며, 이를 올바르게 사용하는 방법을 이해하는 것은 API를 다루는 모든 개발자에게 필수적입니다.
이 가이드에서는 DELETE 메서드의 작동 방식, 사용 시기, 클라이언트 및 서버 측 구현 방법, 피해야 할 일반적인 실수에 대해 자세히 설명합니다.
HTTP DELETE 메서드란 무엇인가요?
DELETE 메서드는 주어진 URI로 식별되는 리소스를 서버가 삭제하도록 요청합니다. DELETE 작업이 성공하면 해당 위치에 리소스가 더 이상 존재하지 않음을 의미합니다.
DELETE /api/v1/users/42 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
서버는 결과에 따라 상태 코드로 응답합니다.
HTTP/1.1 204 No Content
주요 특성
| 속성 | 값 | 설명 |
|---|---|---|
| Request body | 선택 사항 (보통 비어 있음) | 대부분의 구현체에서 body를 무시함 |
| Response body | 선택 사항 | 204는 body가 없으며, 200은 상세 내용을 포함할 수 있음 |
| Idempotent (멱등성) | 예 | 동일한 리소스를 두 번 삭제해도 동일한 결과가 발생함 |
| Safe (안정성) | 아니요 | 서버 상태를 수정함 |
| Cacheable (캐시 가능성) | 아니요 | 응답이 캐시되어서는 안 됨 |
DELETE 응답 상태 코드
| 상태 코드 | 의미 | 사용 시기 |
|---|---|---|
| 200 OK | 리소스 삭제됨; 응답에 상세 내용 포함 | 확인 메시지 body를 반환할 때 |
| 202 Accepted | 삭제가 처리를 위해 대기열에 추가됨 | 비동기 삭제(예: 백그라운드 작업)의 경우 |
| 204 No Content | 리소스 삭제됨; 응답 body 없음 | DELETE에 대한 가장 일반적인 응답 |
| 404 Not Found | 리소스가 존재하지 않음 | 리소스가 생성된 적이 없는 경우 |
| 401 Unauthorized | 인증 필요 | 자격 증명이 누락되었거나 유효하지 않은 경우 |
| 403 Forbidden | 인증되었으나 권한 없음 | 사용자가 삭제 권한이 없는 경우 |
| 409 Conflict | 현재 상태로 인해 삭제 불가 | 예: 리소스에 종속된 레코드가 있는 경우 |
200과 204 중 선택하기
# 204 No Content -- 가장 일반적이며, 응답 body가 없음
DELETE /api/v1/users/42
Response: 204 No Content
# 200 OK -- 삭제된 리소스를 반환하고 싶을 때
DELETE /api/v1/users/42
Response: 200 OK
Body: {"id": 42, "name": "John", "deleted": true, "deletedAt": "2026-02-06T12:00:00Z"}
클라이언트에게 확인 상세 정보가 필요하지 않을 때는 204를 사용하세요. 클라이언트가 삭제된 리소스를 확인하는 것이 유용할 때(예: 실행 취소 기능 또는 감사 로그)는 200을 사용하세요.
DELETE 실전: 클라이언트 측 예시
cURL
# 단순 DELETE 요청
curl -X DELETE https://api.example.com/v1/users/42
# 인증을 포함한 DELETE
curl -X DELETE \
-H "Authorization: Bearer your_token_here" \
https://api.example.com/v1/users/42
# API 키를 포함한 DELETE
curl -X DELETE \
-H "X-API-Key: your_api_key" \
https://api.example.com/v1/users/42
JavaScript (Fetch API)
// 기본 DELETE 요청
const response = await fetch("https://api.example.com/v1/users/42", {
method: "DELETE",
headers: {
"Authorization": "Bearer your_token_here",
},
});
if (response.status === 204) {
console.log("User deleted successfully");
} else if (response.status === 404) {
console.log("User not found");
}
JavaScript (Axios)
import axios from "axios";
try {
await axios.delete("https://api.example.com/v1/users/42", {
headers: {
"Authorization": "Bearer your_token_here",
},
});
console.log("Deleted successfully");
} catch (error) {
if (error.response?.status === 404) {
console.log("User not found");
} else if (error.response?.status === 403) {
console.log("Not authorized to delete this user");
}
}
Python (requests)
import requests
response = requests.delete(
"https://api.example.com/v1/users/42",
headers={"Authorization": "Bearer your_token_here"}
)
if response.status_code == 204:
print("Deleted successfully")
elif response.status_code == 404:
print("User not found")
elif response.status_code == 409:
print("Cannot delete: user has dependent records")
Go
package main
import (
"fmt"
"net/http"
)
func main() {
req, _ := http.NewRequest("DELETE", "https://api.example.com/v1/users/42", nil)
req.Header.Set("Authorization", "Bearer your_token_here")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
switch resp.StatusCode {
case 204:
fmt.Println("Deleted successfully")
case 404:
fmt.Println("User not found")
case 403:
fmt.Println("Not authorized")
}
}
DELETE 실전: 서버 측 예시
Node.js (Express)
import express from "express";
const app = express();
app.delete("/api/v1/users/:id", async (req, res) => {
const { id } = req.params;
// 사용자 존재 여부 확인
const user = await db.users.findById(id);
if (!user) {
return res.status(404).json({ error: "User not found" });
}
// 종속된 레코드 확인
const orders = await db.orders.countByUserId(id);
if (orders > 0) {
return res.status(409).json({
error: "Cannot delete user with active orders",
orderCount: orders,
});
}
// 사용자 삭제
await db.users.deleteById(id);
// 204 No Content 반환
res.status(204).send();
});
Python (FastAPI)
from fastapi import FastAPI, HTTPException
app = FastAPI()
@app.delete("/api/v1/users/{user_id}", status_code=204)
async def delete_user(user_id: int):
user = await db.users.find_by_id(user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
if await db.orders.count_by_user_id(user_id) > 0:
raise HTTPException(
status_code=409,
detail="Cannot delete user with active orders"
)
await db.users.delete_by_id(user_id)
# FastAPI는 body 없이 자동으로 204를 반환합니다.
Go (net/http)
func deleteUserHandler(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
user, err := db.FindUserByID(id)
if err != nil || user == nil {
http.Error(w, "User not found", http.StatusNotFound)
return
}
if err := db.DeleteUser(id); err != nil {
http.Error(w, "Failed to delete user", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
}
멱등성(Idempotency): 왜 중요한가요?
DELETE 메서드는 멱등성을 가집니다. 즉, 동일한 DELETE 요청을 여러 번 수행해도 동일한 결과가 발생하며, 결과적으로 리소스는 삭제된 상태(또는 존재하지 않는 상태)가 됩니다.
DELETE /api/v1/users/42 -> 204 No Content (사용자 삭제됨)
DELETE /api/v1/users/42 -> 204 No Content (이미 삭제됨, 결과 동일)
하지만 약간의 미묘한 차이가 있습니다. 일부 API는 리소스가 더 이상 존재하지 않기 때문에 두 번째 요청에서 404를 반환합니다. 두 접근 방식 모두 유효합니다.
| 접근 방식 | 첫 번째 요청 | 두 번째 요청 | 엄격한 멱등성 유지? |
|---|---|---|---|
| 항상 204 | 204 | 204 | 예 |
| 사라졌으면 404 | 204 | 404 | 기술적으로 예 (리소스가 여전히 삭제된 상태임) |
"항상 204" 방식은 클라이언트가 "방금 삭제됨"과 "이미 삭제됨"을 구분할 필요가 없으므로 더 단순합니다.
소프트 삭제(Soft Delete) vs 하드 삭제(Hard Delete)
프로덕션 시스템에서 많은 팀은 데이터를 실제로 제거하는 대신 소프트 삭제를 구현합니다.
하드 삭제 (Hard Delete)
-- 데이터가 영구적으로 제거됨
DELETE FROM users WHERE id = 42;
소프트 삭제 (Soft Delete)
-- 데이터는 삭제된 것으로 표시되지만 보존됨
UPDATE users SET deleted_at = NOW(), is_active = false WHERE id = 42;
| 접근 방식 | 복구 가능성 | 저장 용량 | 규정 준수 | 복잡성 |
|---|---|---|---|---|
| 하드 삭제 | 아니요 | 공간 절약 | GDPR 친화적 | 단순함 |
| 소프트 삭제 | 예 | 시간이 지남에 따라 증가 | 정기적인 제거 작업 필요 | 중간 |
API 엔드포인트는 클라이언트 입장에서 동일하게 보입니다(DELETE /api/v1/users/42). 차이점은 서버 측 구현 방식에 있습니다.
대량 삭제 (Bulk Delete)
때로는 한 번에 여러 리소스를 삭제해야 합니다. 두 가지 일반적인 패턴이 있습니다.
패턴 1: 쿼리 매개변수 (Query Parameters)
DELETE /api/v1/users?ids=1,2,3,4,5
패턴 2: 요청 Body (Request Body)
curl -X DELETE https://api.example.com/v1/users \
-H "Content-Type: application/json" \
-d '{"ids": [1, 2, 3, 4, 5]}'
패턴 3: 삭제 전용 엔드포인트에 POST 사용
일부 API는 DELETE와 함께 body를 보내는 것을 피하고 대신 POST를 사용합니다.
POST /api/v1/users/bulk-delete
Body: {"ids": [1, 2, 3, 4, 5]}
패턴 3은 일부 HTTP 클라이언트나 프록시가 DELETE 요청에서 body를 제거할 수 있기 때문에 호환성이 가장 높습니다.
일반적인 실수
1. 인증 및 권한 미확인
항상 요청자가 리소스를 삭제할 권한이 있는지 확인하세요.
app.delete("/api/v1/posts/:id", async (req, res) => {
const post = await db.posts.findById(req.params.id);
if (post.authorId !== req.user.id && !req.user.isAdmin) {
return res.status(403).json({ error: "Not authorized" });
}
// ... 삭제 진행
});
2. 연쇄 삭제(Cascading Deletes) 미처리
부모 리소스를 삭제하면 자식 레코드가 고아가 될 수 있습니다. 이를 명시적으로 처리하세요.
// 사용자 및 모든 관련 데이터 삭제
await db.transaction(async (tx) => {
await tx.comments.deleteByUserId(userId);
await tx.posts.deleteByUserId(userId);
await tx.sessions.deleteByUserId(userId);
await tx.users.deleteById(userId);
});
3. 파괴적인 작업에 대한 확인 절차 부재
중요한 리소스의 경우 확인 매개변수를 요구하는 것을 고려하세요.
DELETE /api/v1/projects/42?confirm=true
결론
HTTP DELETE 메서드는 개념상 간단하지만 세심한 구현이 필요합니다. 항상 요청을 인증하고 권한을 부여하며, 예외 상황(누락된 리소스, 종속된 레코드)을 처리하고, 데이터 보존 요구 사항에 따라 소프트 삭제와 하드 삭제 중 하나를 결정하세요.
미디어 생성 API와 상호 작용하는 애플리케이션을 구축하는 경우, Hypereal AI는 생성된 에셋 관리, 업로드된 이미지 제거 또는 완료된 비디오 생성 작업 정리를 위해 DELETE를 사용할 수 있는 깔끔한 REST API를 제공합니다.
