Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 169 minIntermédiaire

Aide-mémoire Node.js, npm et Express

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • retrouver vite une commande, une méthode ou un code de statut ;
  • vérifier ton API avec la checklist avant une démonstration.

Cet aide-mémoire rassemble sur une page les commandes Node.js et npm, le squelette d'une API Express 5, les méthodes de req et res, l'accès à MySQL avec mysql2 et Prisma, la validation avec Zod, l'authentification par JWT, les codes de statut et la checklist à passer avant une démonstration. Chaque ligne renvoie au chapitre qui l'explique.

Commandes node et npm

CommandeEffetChapitre
node fichier.jsExécute un script1
node --watch --env-file=.env src/server.jsRelance à chaque modification, avec les variables du .env2
node --run dev ou npm run devLance le script dev du package.json2
node --test "test/**/*.test.js"Lance les tests12
npm init -y puis npm pkg set type=moduleCrée le projet en modules ES2
npm install paquet / npm install -D paquetDépendance d'exécution / de développement2
npm ci --omit=devInstallation exacte, sans les outils de développement (production, CI)2 et 14
npm outdated, npm update, npm auditVersions en retard, mise à jour compatible, vulnérabilités connues2
npx prisma@7 generateGénère le client Prisma7

Le squelette de l'API

text
applifrais-api/
├── package.json, package-lock.json, .env.example, .gitignore, Dockerfile, compose.yaml
├── docs/openapi.yaml
├── sql/schema.sql, sql/donnees.sql
├── src/
│   ├── server.js        démarrage, arrêt propre
│   ├── app.js           middlewares et routeurs, dans l'ordre
│   ├── config.js        variables d'environnement vérifiées
│   ├── db.js            pool mysql2
│   ├── logger.js        pino et pino-http
│   ├── erreurs.js       ErreurApi, route 404, gestionnaire d'erreurs
│   ├── middlewares/     authentifier, exigerRole, valider, televersement
│   ├── schemas/         schémas Zod
│   ├── routes/          un routeur par ressource
│   ├── controllers/     HTTP : lire req, choisir la réponse
│   ├── services/        règles de gestion
│   └── depots/          SQL
└── test/                tests node:test et Supertest
js
// src/app.js : l'ordre compte
app.use(journalRequetes);                         // 1. journal
app.use(helmet());                                // 2. en-têtes de sécurité
app.use(cors({ origin: config.origineFront }));   // 3. CORS
app.use(express.json({ limit: '100kb' }));        // 4. corps JSON
app.get('/sante', (req, res) => res.json({ statut: 'ok' }));
app.use('/v1/auth', routeurAuth);                 // 5. routes
app.use('/v1/fiches', routeurFiches);
app.use(routeInconnue);                           // 6. 404
app.use(gestionnaireErreurs);                     // 7. erreurs, quatre paramètres

req et res

Lire la requêteValeur
req.params.idParamètre de chemin, chaîne
req.query.etatChaîne de requête, chaîne ou tableau ; lecture seule en Express 5
req.bodyCorps lu par express.json() ; undefined sans corps JSON
req.get('Authorization')Un en-tête
req.ip, req.method, req.originalUrlAdresse du client, méthode, adresse complète
RépondreExemple
Lectureres.json(fiche)
Créationres.status(201).location(`/v1/fiches/${id}`).json(fiche)
Suppressionres.sendStatus(204)
Erreurthrow new ErreurApi(409, 'transition-interdite', "Changement d'état interdit", detail)
En-têteres.set('Cache-Control', 'no-store')

Express 5 : ce qui change

Express 4Express 5
app.get('/docs/*', …)app.get('/docs/*chemin', …) ; '/{*chemin}' inclut la racine
'/rapports/:annee?''/rapports{/:annee}'
try/catch + next(err) dans chaque route asyncInutile : les erreurs async vont au gestionnaire d'erreurs
res.send(201), res.json(obj, 201)res.sendStatus(201), res.status(201).json(obj)
req.query modifiable, req.body vaut {}req.query en lecture seule, req.body vaut undefined
app.options('*', cors())Inutile (et plante) : app.use(cors(…)) suffit

MySQL avec mysql2

js
import mysql from 'mysql2/promise';
export const pool = mysql.createPool({ ...config.db, connectionLimit: 10, decimalNumbers: true, dateStrings: true });

const [lignes] = await pool.execute('SELECT id, mois FROM fiche_frais WHERE id_visiteur = ?', [7]);
const [[{ total }]] = await pool.execute('SELECT COUNT(*) AS total FROM fiche_frais');
const [resultat] = await pool.execute('INSERT INTO … VALUES (?, ?)', [a, b]); // resultat.insertId
// UPDATE, DELETE : resultat.affectedRows
// LIMIT ? OFFSET ? : passer String(limite), String(decalage)

const connexion = await pool.getConnection();
try {
  await connexion.beginTransaction();
  // … connexion.execute(…) …
  await connexion.commit();
} catch (erreur) {
  await connexion.rollback();
  throw erreur;
} finally {
  connexion.release();
}

Erreurs à connaître : ER_DUP_ENTRY (1062) → 409 ; ER_NO_REFERENCED_ROW_2 (1452) ; ECONNREFUSED → MySQL injoignable (chapitre 6).

Prisma 7

js
await prisma.ficheFrais.findMany({ where: { etat: 'CL' }, orderBy: { mois: 'desc' }, skip: 0, take: 20 });
await prisma.ficheFrais.findUnique({ where: { id: 12 }, include: { lignesForfait: true } });
await prisma.ficheFrais.create({ data: { visiteurId: 7, mois: '2026-11' } });
await prisma.ficheFrais.updateMany({ where: { id: 12, etat: 'CR' }, data: { etat: 'CL' } }); // { count }
await prisma.$transaction([requete1, requete2]);
await prisma.$queryRaw`SELECT … WHERE id = ${id}`; // paramétré ; jamais $queryRawUnsafe avec une saisie

Codes : P2002 doublon, P2003 clé étrangère, P2025 introuvable. Installer avec la version : prisma@7, @prisma/client@7 (chapitre 7).

Zod 4

js
z.config(z.locales.fr());
z.strictObject({ mois: z.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/) });  // corps : champs en trop refusés
z.object({ page: z.coerce.number().int().min(1).default(1) });          // chaîne de requête : conversion
z.iso.date(); z.email(); z.enum(['CR', 'CL']); z.number().positive().multipleOf(0.01);
z.stringbool();                                                           // booléen dans l'URL
const r = schema.safeParse(donnees);                                       // r.success, r.data, r.error.issues

Authentification

js
const hachage = await argon2.hash(motDePasse);                 // stockage
await argon2.verify(hachage, motDePasse);                      // connexion
jwt.sign({ role }, secret, { subject: String(id), expiresIn: 900, algorithm: 'HS256' });
jwt.verify(jeton, secret, { algorithms: ['HS256'] });          // jamais jwt.decode pour authentifier
ContrôleOùRefus
Jeton valideauthentifier401 + WWW-Authenticate
RôleexigerRole('comptable')403
PropriétaireService, après lecture en base404

Codes de statut de l'API

CodeQuand
200Lecture ou modification réussie (liste vide comprise)
201Création, avec Location
204Suppression, sans corps
400JSON illisible
401Pas de jeton, jeton invalide ou expiré, identifiants faux
403Rôle insuffisant
404Ressource inexistante ou d'un autre utilisateur ; route inconnue
409Incompatible avec l'état actuel : doublon, transition interdite, fiche clôturée
413, 415Corps ou fichier trop gros ; type de fichier refusé
422Données invalides, avec la liste des champs
429Trop de requêtes
500Erreur imprévue : message générique et numéro d'incident

Checklist avant une démonstration ou un rendu

  • npm ci puis npm test passent sur une copie propre du dépôt.
  • Le .env.example liste toutes les variables ; aucun .env ni secret dans Git.
  • Toutes les entrées passent par un schéma Zod ; aucun req.body transmis tel quel.
  • Toutes les requêtes SQL sont préparées ; aucune concaténation de saisie.
  • Chaque route protégée a authentifier, le bon exigerRole, et le service vérifie le propriétaire.
  • Les mots de passe sont hachés avec argon2 (ou bcrypt) ; aucun secret dans les journaux.
  • Les erreurs 500 ne montrent ni pile, ni SQL ; elles donnent un numéro d'incident.
  • CORS en liste blanche, helmet branché, limitation de débit sur la connexion.
  • /docs à jour avec les routes réelles ; README avec les étapes de démarrage.
  • Même version de Node.js partout (engines, Dockerfile, CI).

Chapitre terminé !

Tu as lu ce chapitre jusqu'au bout. Passe au suivant tant que c'est frais.

chapitres
–
de série
–
du cours
–
Cours suivant : MongoDBRevoir le sommaire du cours