התנגשויות
חלק מהיחידה REST APIs במסלול ה-Node.js של Coddy. שיעור 13 מתוך 25.
משימה שנייה בשם "שליחת החשבונית" מבלבלת את הרשימה, אבל הגוף שלה תקין. זהו 409 Conflict: הבקשה מתנגשת עם מה שכבר שמור. 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 שנדחו אינם משנים דבר.
נסו זאת: שלחו POST עם { "title": "book the meeting room" } אל /api/tasks: תקבלו 409 עם הקוד conflict, כי למשימה 2 קוראים "Book the meeting room". שלחו PATCH /api/tasks/3 עם { "title": "Write the Project Brief" }: תקבלו 409, ו־GET /api/tasks/3 עדיין יציג "Send the invoice". שלחו PUT /api/tasks/2 עם { "title": "Book the meeting room", "done": true }: תקבלו 200, כי זו הכותרת של משימה 2 עצמה. שלחו POST עם { "title": "Order new chairs" }: תקבלו 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 APIs
תרגלו בעצמכם: Express אונליין