Menu
CoddyTech

Conflitti

Fa parte della sezione API REST del percorso Node.js di Coddy. Lezione 13 di 25.

Una seconda attività chiamata "Invia la fattura" rende confuso l'elenco, ma il suo corpo è valido. Questo è un conflitto 409: la richiesta è in contrasto con ciò che è già memorizzato. Un 400 significa che il corpo stesso è errato.

const taken = users.some((u) => u.name.toLowerCase() === name.toLowerCase());
if (taken) {
    return sendError(res, 409, 'conflict', 'That username is taken');
}

Confronta in minuscolo, perché le persone leggono "Ada" e "ada" come lo stesso nome. Quando una richiesta rinomina qualcosa, escludi l'elemento stesso dalla ricerca. Altrimenti, un utente che salva il modulo senza cambiare nome entra in conflitto con sé stesso.

challenge icon

Sfida

Facile

Due attività chiamate "Send the invoice" rendono confuso l’elenco. Rifiuta un titolo che un’altra attività ha già.

  1. In routes/tasks.js, confronta i titoli senza distinguere tra maiuscole e minuscole: "Book the meeting room" e "book the MEETING room" contano come lo stesso titolo.
  2. In POST, dopo che il controllo dello schema è stato superato: se un’attività ha già quel titolo, usa sendError per rispondere con stato 409 e non aggiungere nulla:
    { "error": { "code": "conflict", "message": "A task with this title already exists" } }
  3. Fai lo stesso in PATCH (solo quando il suo corpo invia un title) e in PUT. In queste due route, escludi dalla ricerca l’attività che stai modificando, così un’attività può mantenere il proprio titolo o cambiarne solo le maiuscole. Un PATCH o PUT rifiutato non modifica nulla.

Prova: invia POST { "title": "book the meeting room" } a /api/tasks: ricevi 409 con codice conflict, perché l’attività 2 si chiama "Book the meeting room". PATCH /api/tasks/3 con { "title": "Write the Project Brief" }: 409, e GET /api/tasks/3 mostra ancora "Send the invoice". PUT /api/tasks/2 con { "title": "Book the meeting room", "done": true }: 200, perché è il titolo dell’attività 2. POST { "title": "Order new chairs" }: 201.

Provalo tu

import { Router } from 'express';
import { z } from 'zod';
import { sendError, sendInvalid } from '../errors.js';
import { nextId, tasks } from '../store.js';

const router = Router();

// Un task completo, come lo invia PUT. .strict() rifiuta i campi che non elenca.
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;

Tutte le lezioni di API REST

Esercitati da solo: Playground Express