Express : API JSON
Exemple d’API REST pour une ressource clients. Les accès à la base de données sont décrits dans PostgreSQL, MariaDB ou SQLite.
Conception
Section intitulée « Conception »| Action | Méthode et chemin | Succès | Erreurs |
|---|---|---|---|
| Lister | GET /api/clients |
200 | |
| Lire | GET /api/clients/:id |
200 | 404 |
| Créer | POST /api/clients |
201 | 400, 409 |
| Remplacer | PUT /api/clients/:id |
200 | 400, 404 |
| Modifier | PATCH /api/clients/:id |
200 | 400, 404 |
| Supprimer | DELETE /api/clients/:id |
204 | 404 |
Réponses d’erreur homogènes :
{ "erreur": "email invalide", "champ": "email" }Routeur avec un stockage en mémoire
Section intitulée « Routeur avec un stockage en mémoire »src/routes/clients.js :
import { Router } from 'express';
export const routeur = Router();const clients = new Map();let prochainId = 1;
routeur.get('/', (req, res) => { const page = Math.max(1, Number(req.query.page) || 1); const taille = Math.min(100, Math.max(1, Number(req.query.taille) || 20)); const tous = [...clients.values()]; res.json({ page, taille, total: tous.length, donnees: tous.slice((page - 1) * taille, page * taille), });});
routeur.get('/:id', (req, res) => { const client = clients.get(Number(req.params.id)); if (!client) return res.status(404).json({ erreur: 'client introuvable' }); res.json(client);});
routeur.post('/', (req, res) => { const { nom, email } = req.body ?? {}; if (typeof nom !== 'string' || nom.trim() === '') { return res.status(400).json({ erreur: 'nom obligatoire', champ: 'nom' }); } if (typeof email !== 'string' || !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)) { return res.status(400).json({ erreur: 'email invalide', champ: 'email' }); } const client = { id: prochainId++, nom: nom.trim(), email }; clients.set(client.id, client); res.status(201).location(`/api/clients/${client.id}`).json(client);});
routeur.delete('/:id', (req, res) => { if (!clients.delete(Number(req.params.id))) { return res.status(404).json({ erreur: 'client introuvable' }); } res.sendStatus(204);});Le stockage en mémoire disparaît à l’arrêt du processus : il sert aux essais. Utiliser une base de données pour des données réelles.
Valider les entrées avec Zod
Section intitulée « Valider les entrées avec Zod »Valider toutes les données reçues (corps, paramètres, chaîne de requête) avec une bibliothèque de schémas :
npm install zodimport { z } from 'zod';
const schemaClient = z.object({ nom: z.string().trim().min(1).max(100), email: z.string().email(),});
function valider(schema) { return (req, res, next) => { const resultat = schema.safeParse(req.body); if (!resultat.success) { return res.status(400).json({ erreur: 'données invalides', details: resultat.error.issues.map((i) => ({ champ: i.path.join('.'), message: i.message, })), }); } req.body = resultat.data; next(); };}
routeur.post('/', valider(schemaClient), (req, res) => { // req.body est validé et nettoyé res.status(201).json({ id: 1, ...req.body });});Accès à une base de données
Section intitulée « Accès à une base de données »Exemple avec PostgreSQL et le paquet pg :
npm install pgimport pg from 'pg';
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL, max: 10,});
routeur.get('/:id', async (req, res) => { const { rows } = await pool.query( 'SELECT id, nom, email FROM clients WHERE id = $1', [Number(req.params.id)], ); if (rows.length === 0) return res.status(404).json({ erreur: 'client introuvable' }); res.json(rows[0]);});Les erreurs de base de données remontent au gestionnaire d’erreurs d’Express 5 sans try/catch.
Versionner l’API
Section intitulée « Versionner l’API »import { routeur as clientsV1 } from './routes/clients.js';app.use('/api/v1/clients', clientsV1);Préfixer par une version permet de faire évoluer l’API sans casser les clients existants.
Essayer avec curl
Section intitulée « Essayer avec curl »curl -s -X POST http://127.0.0.1:3000/api/clients \ -H 'Content-Type: application/json' \ -d '{"nom":"Alice","email":"alice@exemple.com"}'curl -s 'http://127.0.0.1:3000/api/clients?page=1&taille=10'curl -i -X DELETE http://127.0.0.1:3000/api/clients/1Tests automatiques
Section intitulée « Tests automatiques »Node.js intègre un lanceur de tests (node --test) ; fetch est disponible sans installation.
test/api.test.js :
import { test } from 'node:test';import assert from 'node:assert/strict';import { app } from '../src/app.js';
test('GET /sante répond ok', async (t) => { const serveur = app.listen(0, '127.0.0.1'); t.after(() => serveur.close()); const { port } = serveur.address();
const reponse = await fetch(`http://127.0.0.1:${port}/sante`); assert.equal(reponse.status, 200); assert.deepEqual(await reponse.json(), { statut: 'ok' });});node --testLe port 0 laisse le système choisir un port libre : les tests ne rentrent pas en conflit.
Dépannage
Section intitulée « Dépannage »| Symptôme | Piste |
|---|---|
req.body est undefined |
express.json() absent, ou requête sans Content-Type: application/json |
SyntaxError: Unexpected token dans les journaux |
Corps JSON invalide : le gestionnaire d’erreurs renvoie 400 si err.type === 'entity.parse.failed' |
PayloadTooLargeError |
Augmenter limit avec prudence |
| Id non numérique accepté | Valider les paramètres de chemin |
| Connexion à la base refusée | DATABASE_URL, serveur démarré, droits (voir PostgreSQL : utilisateurs et droits) |
| Les tests ne se terminent pas | Serveur non fermé : t.after(() => serveur.close()) |
Ubuntu 24.04 et 26.04 : différences à connaître
Section intitulée « Ubuntu 24.04 et 26.04 : différences à connaître »Aucune propre à la distribution. node --test et fetch demandent Node.js 18 ou plus récent.