Menu
CoddyTech

충돌

Coddy Node.js 여정의 REST API 섹션에 포함된 레슨. 18개 중 9번째.

"Send the invoice"라는 두 번째 작업 때문에 목록이 혼란스러워 보이지만, 본문은 유효합니다. 이는 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"를 같은 이름으로 여기므로 소문자로 비교합니다. 요청에서 어떤 항목의 이름을 바꾸는 경우, 검색할 때 해당 항목 자체는 건너뜁니다. 그렇지 않으면 이름을 바꾸지 않고 양식을 저장하는 사용자도 자기 자신과 충돌하게 됩니다.

challenge icon

챌린지

쉬움
  • 다른 작업에 이미 있는 제목은 대소문자를 구분하지 않으며, POST 요청에서는 코드 conflict와 함께 409 응답을 반환하고, PATCH 및 PUT 요청에서 제목을 설정할 때도 마찬가지입니다.
  • 작업은 자신의 제목을 유지할 수 있습니다.

{ "title": "book the meeting room" }을 사용해 POST 요청을 보낸 다음, { "title": "Book the meeting room", "done": true }를 사용해 PUT /api/tasks/2 요청을 보내세요.

직접 해보기

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의 모든 레슨