التعارضات
جزء من قسم واجهات REST API في رحلة Node.js على Coddy. الدرس 9 من 18.
تجعل مهمة ثانية تُسمى «أرسل الفاتورة» القائمة مربكة، لكن متنها صالح. هذا هو 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" على أنهما الاسم نفسه. عندما يغيّر الطلب اسم شيء ما، استثنِ العنصر نفسه من البحث. وإلا فسيحدث تعارض مع نفسه لمستخدم يحفظ النموذج دون تغيير اسمه.
التحدي
سهل- يؤدي العنوان الذي تستخدمه مهمة أخرى، بأي حالة أحرف، إلى الاستجابة بالرمز 409 مع الرمز
conflict: عند استخدام POST، وكذلك عند استخدام PATCH وPUT إذا حدّدا عنوانًا. - يمكن للمهمة الاحتفاظ بعنوانها الخاص.
أرسل POST مع { "title": "book the meeting room" }، ثم PUT /api/tasks/2 مع { "title": "Book the meeting room", "done": true }.
جرّب بنفسك
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;