충돌
Coddy Node.js 여정의 REST API 섹션에 포함된 레슨. 25개 중 13번째.
"청구서 보내기"라는 두 번째 작업 때문에 목록이 혼란스러워 보이지만, 본문은 올바릅니다. 이는 409 충돌입니다. 요청이 이미 저장된 내용과 충돌합니다. 400은 본문 자체가 잘못되었다는 뜻입니다.
const taken = users.some((u) => u.name.toLowerCase() === name.toLowerCase());
if (taken) {
return sendError(res, 409, 'conflict', 'That username is taken');
}사람들은 "Ada"와 "ada"를 같은 이름으로 보기 때문에 소문자로 비교하세요. 요청으로 무언가의 이름을 바꿀 때는 검색에서 해당 항목 자체를 제외하세요. 그렇지 않으면 이름을 바꾸지 않고 양식을 저장한 사용자가 자기 자신과 충돌하게 됩니다.
챌린지
쉬움"Send the invoice"라는 이름의 작업이 두 개 있으면 목록이 혼란스러워집니다. 다른 작업에 이미 있는 제목은 거부하세요.
routes/tasks.js에서 대소문자를 구분하지 않고 제목을 비교하세요. "Book the meeting room"과 "book the MEETING room"은 같은 제목으로 간주합니다.- POST에서 스키마 검사가 통과한 후, 작업 중 이미 해당 제목을 가진 작업이 있으면
sendError를 사용해 상태 코드 409로 응답하고 아무것도 추가하지 마세요:{ "error": { "code": "conflict", "message": "A task with this title already exists" } } - PATCH(
title을 보내는 경우에만)와 PUT에서도 똑같이 하세요. 이 두 경로에서는 검색할 때 변경 대상 작업을 제외해야 합니다. 따라서 작업은 자체 제목을 유지하거나 대소문자만 바꿀 수 있습니다. 거부된 PATCH 또는 PUT은 아무것도 변경하지 않습니다.
직접 해보기: { "title": "book the meeting room" }을 /api/tasks에 POST하면, 작업 2의 이름이 "Book the meeting room"이므로 코드가 conflict인 409 응답을 받습니다. { "title": "Write the Project Brief" }를 사용해 PATCH /api/tasks/3을 보내면 409 응답을 받고, GET /api/tasks/3을 보내도 여전히 "Send the invoice"가 표시됩니다. { "title": "Book the meeting room", "done": true }를 사용해 PUT /api/tasks/2를 보내면 작업 2의 자체 제목이므로 200 응답을 받습니다. { "title": "Order new chairs" }를 POST하면 201 응답을 받습니다.
직접 해보기
import { Router } from 'express';
import { z } from 'zod';
import { sendError, sendInvalid } from '../errors.js';
import { nextId, tasks } from '../store.js';
const router = Router();
// PUT이 보내는 전체 작업입니다. .strict()는 목록에 없는 필드를 허용하지 않습니다.
const Task = z
.object({
title: z.string().trim().min(1).max(100),
done: z.boolean(),
})
.strict();
const NewTask = Task.partial({ done: true });
const TaskChanges = Task.partial();
router.get('/', (req, res) => {
res.json(tasks);
});
router.get('/:id', (req, res) => {
const task = tasks.find((t) => t.id === Number(req.params.id));
if (!task) {
return sendError(res, 404, 'not_found', 'Task not found');
}
res.json(task);
});
router.post('/', (req, res) => {
const result = NewTask.safeParse(req.body);
if (!result.success) {
return sendInvalid(res, result.error);
}
const { title, done = false } = result.data;
const task = { id: nextId(), title, done, createdAt: new Date().toISOString() };
tasks.push(task);
res.status(201).location(`/api/tasks/${task.id}`).json(task);
});
router.patch('/:id', (req, res) => {
const task = tasks.find((t) => t.id === Number(req.params.id));
if (!task) {
return sendError(res, 404, 'not_found', 'Task not found');
}
const result = TaskChanges.safeParse(req.body);
if (!result.success) {
return sendInvalid(res, result.error);
}
Object.assign(task, result.data);
res.json(task);
});
router.put('/:id', (req, res) => {
const index = tasks.findIndex((t) => t.id === Number(req.params.id));
if (index === -1) {
return sendError(res, 404, 'not_found', 'Task not found');
}
const result = Task.safeParse(req.body);
if (!result.success) {
return sendInvalid(res, result.error);
}
const { id, createdAt } = tasks[index];
tasks[index] = { id, ...result.data, createdAt };
res.json(tasks[index]);
});
router.delete('/:id', (req, res) => {
const index = tasks.findIndex((t) => t.id === Number(req.params.id));
if (index === -1) {
return sendError(res, 404, 'not_found', 'Task not found');
}
tasks.splice(index, 1);
res.status(204).end();
});
export default router;
REST API의 모든 레슨
직접 연습해 보세요: Express 플레이그라운드