Menu
CoddyTech

Schémas avec zod

Fait partie de la section API REST du Journey Node.js de Coddy. Leçon 7 sur 18.

Les vérifications manuelles se multiplient avec chaque champ et chaque route. Un schéma zod décrit une fois la structure, et safeParse teste une valeur par rapport à celle-ci :

import { z } from 'zod';

const Event = z.object({
    name: z.string().trim().min(1).max(80),
    seats: z.number().int().min(1),
}).strict();

const result = Event.safeParse(req.body);
if (!result.success) {
    return res.status(400).json({ error: z.flattenError(result.error) });
}
// result.data contient le corps de la requête validé, avec name sans espaces superflus

.strict() rejette les champs qui ne figurent pas dans le schéma. Event.partial() rend chaque champ facultatif, ce dont a besoin une requête PATCH, et Event.partial({ seats: true }) ne rend facultatif que seats. z.flattenError répertorie les problèmes par champ. Event.parse effectue la même vérification, mais lève une exception si la valeur est invalide, et une exception dans une route devient une erreur 500.

challenge icon

Défi

Facile
  • Remplace les contrôles manuels par un schéma Task : title, une chaîne sans espaces superflus de 1 à 100 caractères, done, un booléen, et .strict().
  • POST (done facultatif, valeur par défaut false), PATCH (tous les champs facultatifs) et PUT utilisent safeParse ; un corps invalide renvoie une réponse 400 avec z.flattenError(result.error) comme error.

Envoie une requête POST avec { "title": " Order new chairs ", "color": "red" }, puis sans color.

Essayez vous-même

import { Router } from 'express';
import { nextId, tasks } from '../store.js';

const router = Router();

// La raison pour laquelle un titre ne peut pas être stocké, ou null s’il peut l’être.
const titleError = (title) => {
    if (typeof title !== 'string' || title.trim() === '' || title.length > 100) {
        return 'title must be a string of 1 to 100 characters';
    }
    return null;
};

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 res.status(404).json({ error: 'Task not found' });
    }
    res.json(task);
});

router.post('/', (req, res) => {
    const { title } = req.body ?? {};
    const problem = titleError(title);
    if (problem) {
        return res.status(400).json({ error: problem });
    }
    const task = { id: nextId(), title, done: false, 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 res.status(404).json({ error: 'Task not found' });
    }
    const { title, done } = req.body ?? {};
    if (title !== undefined && titleError(title)) {
        return res.status(400).json({ error: titleError(title) });
    }
    if (done !== undefined && typeof done !== 'boolean') {
        return res.status(400).json({ error: 'done must be true or false' });
    }
    if (title !== undefined) task.title = title;
    if (done !== undefined) task.done = done;
    res.json(task);
});

router.put('/:id', (req, res) => {
    const index = tasks.findIndex((t) => t.id === Number(req.params.id));
    if (index === -1) {
        return res.status(404).json({ error: 'Task not found' });
    }
    const { title, done } = req.body ?? {};
    if (title === undefined || done === undefined) {
        return res.status(400).json({ error: 'PUT needs every field: title and done' });
    }
    const { id, createdAt } = tasks[index];
    tasks[index] = { id, title, done, 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 res.status(404).json({ error: 'Task not found' });
    }
    tasks.splice(index, 1);
    res.status(204).end();
});

export default router;

Toutes les leçons de API REST