Menu
CoddyTech

Xung đột

Thuộc phần REST API trong hành trình Node.js của Coddy. Bài học 13 trên 25.

Nhiệm vụ thứ hai có tên "Gửi hóa đơn" khiến danh sách trở nên khó hiểu, nhưng phần nội dung của nó hợp lệ. Đó là 409 Conflict: yêu cầu xung đột với dữ liệu đã được lưu. Lỗi 400 có nghĩa là bản thân phần nội dung không hợp lệ.

const taken = users.some((u) => u.name.toLowerCase() === name.toLowerCase());
if (taken) {
    return sendError(res, 409, 'conflict', 'That username is taken');
}

So sánh ở dạng chữ thường, vì mọi người xem "Ada" và "ada" là cùng một tên. Khi một yêu cầu đổi tên một thứ gì đó, hãy bỏ qua chính mục đó khi tìm kiếm. Nếu không, người dùng lưu biểu mẫu mà không đổi tên sẽ gặp xung đột với chính mình.

challenge icon

Thử thách

Dễ

Hai tác vụ có tên "Send the invoice" khiến danh sách khó hiểu. Từ chối tiêu đề đã được tác vụ khác sử dụng.

  1. Trong routes/tasks.js, so sánh tiêu đề mà không phân biệt chữ hoa chữ thường: "Book the meeting room" và "book the MEETING room" được xem là cùng một tiêu đề.
  2. Trong POST, sau khi kiểm tra schema thành công: nếu đã có tác vụ mang tiêu đề đó, hãy dùng sendError để phản hồi với trạng thái 409 và không thêm gì cả:
    { "error": { "code": "conflict", "message": "A task with this title already exists" } }
  3. Làm tương tự trong PATCH (chỉ khi phần thân gửi title) và trong PUT. Ở hai route này, hãy loại tác vụ đang được thay đổi khỏi kết quả tìm kiếm, để tác vụ có thể giữ nguyên tiêu đề hoặc chỉ đổi chữ hoa, chữ thường. Nếu PATCH hoặc PUT bị từ chối, không có gì thay đổi.

Thử nhé: POST { "title": "book the meeting room" } tới /api/tasks: bạn nhận được 409 với mã conflict, vì tác vụ 2 có tên "Book the meeting room". PATCH /api/tasks/3 với { "title": "Write the Project Brief" }: 409, và GET /api/tasks/3 vẫn hiển thị "Send the invoice". PUT /api/tasks/2 với { "title": "Book the meeting room", "done": true }: 200, vì đó là tiêu đề của chính tác vụ 2. POST { "title": "Order new chairs" }: 201.

Tự làm thử

import { Router } from 'express';
import { z } from 'zod';
import { sendError, sendInvalid } from '../errors.js';
import { nextId, tasks } from '../store.js';

const router = Router();

// Một tác vụ đầy đủ, đúng như PUT gửi đến. .strict() từ chối các trường không có trong danh sách của nó.
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;

Tất cả bài học trong REST API

Tự luyện tập: Playground Express