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.
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.
- 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 đề. - 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" } } - 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
1Tài nguyên và động từ
Tài nguyên và URLTạo tài nguyên bằng POST4Trình duyệt, phiên bản và cấu hình
Origin là gìCho phép một origin khácCORSTrang gọi APITự luyện tập: Playground Express