Menu
CoddyTech

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.

challenge icon

Défi

Facile

Deux tâches appelées "Send the invoice" rendent la liste confuse. Refuse un titre qu’une autre tâche possède déjà.

  1. 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.
  2. Dans POST, après la validation du schéma : si une tâche possède déjà ce titre, utilise sendError pour répondre avec le statut 409, sans rien ajouter :
    { "error": { "code": "conflict", "message": "A task with this title already exists" } }
  3. 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

Entraînez-vous par vous-même : Playground Express