Conflits
Fait partie de la section API REST du Journey Node.js de Coddy. Leçon 13 sur 25.
Une deuxième tâche appelée « Envoyer la facture » rend la liste confuse, mais son corps est valide. Il s’agit d’un 409 Conflict : la requête entre en conflit avec ce qui est déjà enregistré. Un 400 signifie que le corps lui-même est incorrect.
const taken = users.some((u) => u.name.toLowerCase() === name.toLowerCase());
if (taken) {
return sendError(res, 409, 'conflict', 'That username is taken');
}Comparez en minuscules, car les gens considèrent que « Ada » et « ada » désignent le même nom. Lorsqu’une requête renomme un élément, ignorez cet élément dans la recherche. Sinon, un utilisateur qui enregistre le formulaire sans modifier son nom entre en conflit avec lui-même.
Défi
FacileDeux tâches appelées "Send the invoice" rendent la liste confuse. Refuse un titre qu’une autre tâche possède déjà.
- Dans
routes/tasks.js, compare les titres sans tenir compte de la casse : "Book the meeting room" et "book the MEETING room" comptent comme un seul et même titre. - Dans POST, après la validation du schéma : si une tâche possède déjà ce titre, utilise
sendErrorpour répondre avec le statut 409, sans rien ajouter :{ "error": { "code": "conflict", "message": "A task with this title already exists" } } - Fais de même dans PATCH (uniquement lorsque son corps envoie un
title) et dans PUT. Dans ces deux routes, exclue la tâche modifiée de la recherche, afin qu’une tâche puisse conserver son propre titre ou ne changer que ses majuscules et minuscules. Un PATCH ou PUT refusé ne modifie rien.
Essaie : envoie POST { "title": "book the meeting room" } à /api/tasks : tu obtiens 409 avec le code conflict, car la tâche 2 s’appelle "Book the meeting room". Envoie PATCH /api/tasks/3 avec { "title": "Write the Project Brief" } : 409, et GET /api/tasks/3 affiche toujours "Send the invoice". Envoie PUT /api/tasks/2 avec { "title": "Book the meeting room", "done": true } : 200, car c’est le propre titre de la tâche 2. Envoie POST { "title": "Order new chairs" } : 201.
Essayez vous-même
import { Router } from 'express';
import { z } from 'zod';
import { sendError, sendInvalid } from '../errors.js';
import { nextId, tasks } from '../store.js';
const router = Router();
// Une tâche complète, telle que PUT l’envoie. .strict() refuse les champs qu’elle ne répertorie pas.
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;
Toutes les leçons de API REST
1Ressources et verbes
Ressources et URLCréer avec POST4Navigateurs, versions et configuration
Qu’est-ce qu’une origineAutoriser une autre origineCORSUne page qui appelle l’APIEntraînez-vous par vous-même : Playground Express