반응형
서론
Redux Toolkit Query(RTK Query)를 활용해 API를 호출하다 보면, 종종 요청 본문 구조가 서버와 일치하지 않아 예상치 못한 오류가 발생하는 경우가 있습니다.
그중 자주 마주치는 이슈 중 하나가 바로 "body wrapper" 문제입니다 — 즉, 본문에 포함되면 안 되는 키가 들어가거나, 데이터 구조가 불필요하게 중첩되는 현상이죠.
이번 글에서는 그 원인 중 하나인 body 구조의 불일치 문제, 특히 JSON.stringify()의 오용으로 인해 서버가 기대하는 본문 구조와 달라지는 사례를 다룹니다.
서버 요구 사항
- 서버는 다음과 같은 요청 구조를 기대합니다.
// 서버가 정의한 타입
interface ContentUpdateRequest {
id: number; // URL 경로에 사용됨
data: Record<string, any>; // 요청 본문(body)
}
- 희망 JSON body (id는 URL 경로(/api/content/:id/)에만 사용되며, body에는 절대 포함되어선 안 됩니다.)
{
"data": {
"title": "콘텐츠 제목",
"description": "설명",
"fields": [...],
"entries": [...]
}
}
잘못된 구현: body를 수동으로 stringify
- 이 코드에서는 body를 문자열로 직접 직렬화(JSON.stringify)하고 있습니다. 이로 인해 다음과 같은 문제가 생깁니다
updateContent: builder.mutation<
ContentUpdateResponse,
ContentUpdateRequest
>({
query: ({ id, ...body }) => ({
url: `/api/content/${id}/`,
method: "PATCH",
body: JSON.stringify(body), // ❌ stringify는 필요 없음
}),
}),
| 문제 | 설명 |
| Content-Type 누락 | 자동 설정되지 않아 서버가 text/plain으로 인식 |
| 자동 직렬화 무력화 | RTK Query의 fetchBaseQuery가 JSON임을 감지하지 못함 |
| 서버 파싱 실패 | 서버가 예상한 data 객체를 파싱하지 못하거나 무시함 |
잘못된 구현: 구조 분해(...body) 없이 body 전달
- 이 코드에서는 body를 구조 분해(...) 없이 직접 전달하고 있습니다. 이로 인해 다음과 같은 문제가 생깁니다
updateContent: builder.mutation<
ContentUpdateResponse,
ContentUpdateRequest
>({
query: ({ id, body }) => ({
url: `/api/content/${id}/`,
method: "PATCH",
body: body, // ❌ 구조분해 없음
}),
}),
// 서버 전달 시 호출 형태
updateContent({
id: 123,
body: {
data: { ... }
}
});
→ 본문의 루트 키가 body로 감싸진 중첩 구조가 만들어집니다 ❌
{
"body": {
"data": {
"title": "...",
...
}
}
}
올바른 구현
- id는 구조 분해로 추출되어 URL에만 사용됩니다.
- 나머지 모든 필드(data 등)는 body 객체로 본문에 직렬화 없이 전달됩니다.
- RTK Query의 fetchBaseQuery는 이 객체를 자동으로 JSON.stringify하고, Content-Type: application/json도 자동 설정합니다.
// mutation 정의
updateContent: builder.mutation<
ContentUpdateResponse,
ContentUpdateRequest
>({
query: ({ id, ...body }) => ({
url: `/api/content/${id}/`,
method: "PATCH",
body, // ✅ 객체 그대로 전달
}),
});
// 호출부 예시
const handleSubmit = async () => {
if (!title.trim()) return showError("제목을 입력해주세요.");
if (!rcmRows || rcmRows.length === 0) {
return showError("RCM 파일을 업로드해주세요.");
}
const body = {
title,
description,
columns: header,
records: rcmRows,
};
try {
await updateRcm({ id: contentId, ...body }).unwrap(); // ✅ id는 path param, body는 JSON
success("RCM이 성공적으로 수정되었습니다.");
navigate(`/rcm`);
} catch (err) {
showError("수정 중 오류가 발생했습니다.");
}
};
정리: 구조 분해는 단순한 문법이 아니다.
| 체크 항목 | 설명 |
| id는 URL 전용 | body에 포함시키지 않는다 |
| data는 JSON root로 | 서버 명세와 일치하게 구성 |
| JSON.stringify() 직접 호출 X | RTK Query가 자동 처리함 |
| 구조 분해(...)는 필수 | id와 body를 명확히 분리하기 위해 |

728x90
반응형
'Develop' 카테고리의 다른 글
| [Github] 계정 및 레포지토리별 SSH Key 분리 전략 및 설정 (3) | 2025.07.11 |
|---|---|
| [Python] 텔레그램 채널 알림 시스템(봇) 만들기 (with BotFather) (0) | 2025.06.17 |
| [nodejs] CORS Preflight 완전 정복 - 브라우저가 먼저 “실례합니다!” (1) | 2025.06.04 |
| [React] dyld: Library not loaded: /usr/local/homebrew/opt/icu4c/lib/libicui18n.74.dylib (0) | 2024.12.10 |
| [Python] except 상세 logging 방법 (1) | 2024.11.27 |
| [Python] SyntaxError: f-string: unmatched '[' 원인 및 해결 방법 (0) | 2024.11.26 |
| [Docker] docker-compose 실행 및 중지, 테스트 방법 (0) | 2024.11.25 |
| [Python] extend()로 List 자료형에 List 요소 추가하기 (2) | 2024.11.18 |