Services Web - Notes de cours complètes
Cégep de Victoriaville - Hiver 2026
Enseignants : Mathieu Fréchette & Sébastien Trottier
TABLE DES MATIÈRES
- Environnement de développement
- API - Introduction
- API - Bonnes pratiques
- API - Le format JSON
- API - Documentation avec OpenAPI
- API - Validation et génération de code
- Node.js - Introduction
- Node.js - Serveur Web
- Node.js - NPM
- Node.js - Express.js
- Node.js - Intergiciel (Middleware)
- Node.js - Routage
- Node.js - Structure d'un projet Express
- Node.js - Asynchronisme en JavaScript
- Node.js - MySQL avec Express.js
- Déploiement - Render
- Déploiement - PostgreSQL
- Frontend - Fetch API
- Frontend - Toolpad Studio
- Frontend - Variables d'environnement avec Toolpad
- Sécurité - Clé API
- Sécurité - Hashage de mot de passe
- Sécurité - JWT
- Sécurité - Authentification avec JWT
- Aide - Démarrer un projet Express.js
- Aide - Récupérer les paramètres d'une requête
- Aide - Erreur avec CORS
- Exercices
Environnement de développement
Langage : JavaScript, JSON (données sérialisées)
IDE : Visual Studio Code
SGBD et outils : - NodeJS : serveur exécutant le JavaScript - Devilbox : Pile complète de développement LAMP, pour lancer Node.js et les bases de données (MySQL ou PostgreSQL) - DBeaver : Outil de gestion des bases de données
Client API : Postman — logiciel pour interroger et tester des APIs
API - Introduction
C'est quoi un service web (API) ?
L'acronyme API signifie Application Programming Interface. On peut définir une API comme une interface qui permet d'échanger de l'information avec d'autres applications, peu importe le langage de programmation et le système d'exploitation.
Il existe plusieurs types d'architectures d'API. Le cours porte sur l'architecture REST qui se base sur les standards du web et le protocole HTTP.
Fonctionnement général
L'API va recevoir des requêtes HTTP qui seront traitées selon les paramètres envoyés et retournera ensuite une réponse à la requête.
Une requête à un API REST est toujours composée de quatre parties essentielles :
- Method : Une méthode HTTP — GET, POST, PUT, PATCH, DELETE
- Endpoint : Une porte d'entrée (endpoint), le plus souvent sous la forme d'une URL
- Headers : Une entête à la requête HTTP contenant plusieurs informations (infos de l'usager, clé d'API...)
- Body : Un "corps" qui peut contenir des informations à envoyer à l'API
Envoyer une requête
On peut envoyer une requête à un API depuis n'importe quel navigateur, mais on sera limité à la méthode GET. Des outils comme Postman permettent une utilisation plus poussée. La plupart des langages de programmation prennent en charge les requêtes HTTP.
API - Bonnes pratiques
Utiliser le format JSON
Le format à préconiser pour recevoir et retourner de l'information est le format JSON.
Utiliser des noms au lieu de verbes pour les routes
Ne pas utiliser de verbes dans les noms de routes.
Mauvais :
https://monApi/getUtilisateurs
https://monApi/createUtilisateurs
Bon :
https://monApi/utilisateurs
Une même route peut avoir plus d'une fonctionnalité — c'est la méthode HTTP (GET, POST, etc.) qui les différencie.
Utiliser des paramètres directement dans l'URL
Deux façons d'inclure des paramètres :
Dans la section query de l'URL :
https://monApi/utilisateurs?code=1
Directement dans la route :
https://monApi/utilisateurs/1
La seconde méthode ne devrait pas être utilisée avec plus d'un paramètre. Attention aux conflits de routes.
Utiliser la méthode HTTP pour décrire la fonctionnalité
| Opération BD | Méthode HTTP |
|---|---|
| Lire | GET |
| Insérer | POST |
| Modifier | PUT |
| Modifier partiellement | PATCH |
| Supprimer | DELETE |
Attention : On ne doit jamais modifier une ressource avec une méthode GET.
PUT vs PATCH : PUT modifie toutes les valeurs de la ressource (et crée si inexistante). PATCH ne modifie qu'une partie.
Exemple de routes avec méthodes :
| Méthode | Route | Description |
|---|---|---|
| GET | /utilisateurs | Retourne la liste de tous les utilisateurs |
| POST | /utilisateurs | Création d'un utilisateur |
| PUT | /utilisateurs/21 | Modification de l'utilisateur id 21 |
| DELETE | /utilisateurs/21 | Suppression de l'utilisateur id 21 |
Utiliser les codes de statut HTTP
- 200-299 : Succès
- 400-499 : Erreur côté client
- 500-599 : Erreur côté serveur
Résumé des codes de statut suggérés
| Méthode | GLOBAL (/utilisateurs) | Sur un élément (/utilisateurs/{id}) |
|---|---|---|
| POST | 201 Created | - |
| GET | 200 OK | 200 OK / 404 Not Found |
| PUT | 405 Method Not Allowed | 200 OK / 201 Created |
| PATCH | 405 Method Not Allowed | 200 OK / 404 Not Found |
| DELETE | 405 Method Not Allowed | 204 No Content / 200 OK / 404 Not Found |
Codes d'erreur
| Code | Utilisation |
|---|---|
| 400 Bad Request | Requête mal formulée (paramètre manquant) |
| 401 Unauthorized | Client non authentifié |
| 403 Forbidden | Client authentifié mais pas autorisé |
| 404 Not Found | URL invalide / ressource inexistante |
| 500 Internal Server Error | Erreur côté serveur |
Utiliser l'imbrication dans les routes
Quand des ressources sont reliées, utiliser l'imbrication : /auteurs/{auteur_id}/livres retourne tous les livres d'un auteur. Ne pas aller trop en profondeur.
Filtres, tri, sélection et pagination
Filtre
/livres?categorie=roman
/livres?categorie=roman,documentaire
Tri
/livres?tri=+titre,-prix
Sélection des champs retournés
/livres?champs=titre,isbn
Pagination
/livres?offset=100&limit=50
/livres?page=3
Toujours ajouter dans la réponse le nombre total d'enregistrements, les valeurs de limit et offset.
API - Le format JSON
JSON (JavaScript Object Notation) est un format créé pour faciliter l'échange de données entre différents systèmes. Indépendant de tout langage.
Structure de base
L'objet
Suite d'éléments clé/valeurs entre accolades :
{
"cle1": "valeur1",
"cle2": "valeur2"
}
Le tableau
Suite d'éléments ordonnés entre crochets :
["valeur1", "valeur2"]
On peut les mélanger (tableau d'objets, objet contenant un tableau, etc.).
Valeurs possibles
- string : texte entouré de guillemets
" - number : sans guillemets, décimales avec le point, exposants avec
e - object : entre accolades
- array : entre crochets
- boolean :
trueoufalse(sans guillemets, minuscule) - null : sans guillemets, minuscule
Dates : Pas de format standard en JSON. Utiliser le format ISO 8601 en texte.
Valider du JSON
- JSONLint : https://jsonlint.com/
- VSCode : validation automatique dans les fichiers
.json
Utilisation en JavaScript
JSON.stringify()
Convertir un objet JavaScript en JSON :
const usager = {prenom: "Mathieu", nom: "Frechette", age: 43};
const jsToJSON = JSON.stringify(usager);
JSON.parse()
Convertir du JSON en objet JavaScript :
const objJS = JSON.parse(jsToJSON);
console.log(objJS.prenom); // Mathieu
API - Documentation avec OpenAPI
La documentation est essentielle. Elle doit comporter : - Une liste de toutes les routes avec description - Les informations d'authentification s'il y a lieu - Pour chaque route, les paramètres à ajouter - Pour chaque route, toutes les réponses possibles (code de statut, données retournées)
On utilise la norme OpenAPI Specification (OAS) avec Swagger UI Express pour le rendu visuel.
Installation
npm install swagger-ui-express
Créer ./src/config/documentation.json :
{
"openapi": "3.1.0",
"info": {
"title": "Démo API",
"version": "1.0.0"
}
}
Dans le fichier de démarrage :
import express from 'express';
import swaggerUi from 'swagger-ui-express';
import fs from 'fs';
const swaggerDocument = JSON.parse(fs.readFileSync('./src/config/documentation.json', 'utf8'));
const swaggerOptions = {
customCss: '.swagger-ui .topbar { display: none }',
customSiteTitle: "Demo API"
};
const app = express();
app.use('/api/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument, swaggerOptions));
Composition du fichier de documentation
{
"openapi": "3.1.0",
"info": { },
"servers": [ ],
"paths": { }
}
Section info
"info": {
"title": "Titre [REQUIS]",
"summary": "Courte description",
"description": "Description détaillée",
"contact": {
"name": "API Support",
"url": "https://www.example.com/support",
"email": "[email protected]"
},
"version": "1.0.1 [REQUIS]"
}
Section servers
"servers": [
{ "url": "http://localhost:3000/", "description": "Serveur de développement" },
{ "url": "http://api.profs.ca", "description": "Serveur en ligne" }
]
Section paths
Chaque route est détaillée avec : description, tags, paramètres, réponses.
Chaque méthode d'une même route va dans la même entrée paths. Voir le fichier documentation.json complet dans les notes de cours pour un exemple exhaustif avec GET, POST, paramètres query, path, et requestBody.
API - Validation et génération de code
OAS permet l'automatisation de : - La validation des paramètres d'entrée et de sortie - La génération de code pour les routes et contrôleurs Express - La génération de code pour les clients de l'API
Validation avec express-openapi-validator
Middleware Express qui vérifie automatiquement chaque requête entrante/sortante selon la spécification OAS.
npm install express-openapi-validator
import * as OpenApiValidator from 'express-openapi-validator';
app.use(
OpenApiValidator.middleware({
apiSpec: path.join(__dirname, './documentation.json'),
validateRequests: true,
validateResponses: true
})
);
// Middleware d'erreur (4 arguments)
app.use((err, req, res, next) => {
res.status(err.status || 500).json({
message: err.message,
errors: err.errors,
});
});
Erreur de validation requête → 400. Erreur de validation réponse → 500.
Génération de code serveur
npx @openapitools/openapi-generator-cli generate \
-i users_openapi_doc.json \
-g nodejs-express-server \
-o ./generated-server
Fichiers générés : controllers, routes, et index.js avec OpenApiValidator intégré.
Node.js - Introduction
JavaScript est normalement interprété par le navigateur. Avec Node.js, on peut lancer du JavaScript directement sur la machine (côté serveur/backend). Node.js fournit un environnement d'exécution (runtime) sans lien avec les navigateurs.
Installation
Téléchargement : https://nodejs.org/en
Exécuter du code JavaScript sur un serveur
// hello.js
console.log('Code qui s\'exécute sur le serveur');
var fruits = ['pomme', 'orange', 'banane'];
fruits.forEach((fruit, index) => {
console.log(`${index + 1} : ${fruit}`);
});
Lancer avec : node hello.js
Node.js - Serveur Web
Hello World dans un serveur web
import { createServer } from 'node:http';
const hostname = '127.0.0.1';
const port = 3000;
const server = createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World - serveur NodeJS');
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
- Le module
httpde Node gère le serveur - req = requête HTTP reçue, res = réponse retournée
listen()lance le serveur en mode écoute
L'URL et ses paramètres
import url from 'url';
const params = url.parse(req.url, true).query;
console.log(params["page"]); // Valeur du paramètre page
La variable params est un tableau associatif avec le nom des paramètres en clé.
Gestion des routes
const route = url.parse(req.url).pathname;
if (route == '/') {
res.write("Bienvenue");
} else if (route == '/salle-serveurs') {
res.write('Salle des serveurs');
} else {
res.write('Page introuvable !');
res.statusCode = 404;
}
res.end();
Node.js - NPM
npm est le gestionnaire de paquets officiel de Node.js. Installé automatiquement avec Node.js depuis la version 0.6.3.
npm init
npm init -y
Crée le fichier package.json. IMPORTANT : Ajouter "type": "module" dans package.json.
npm init -y && npm pkg set type="module"
npm install
npm install nomModule
- Crée le dossier
node_modules - Ajoute le module et ses dépendances dans
package.json - Ne jamais fournir
node_modulesen partage (ajouter au.gitignore) npm install(sans nom de module) installe tous les modules manquants selonpackage.json
npm install --save-dev
Pour les modules de développement uniquement :
npm install nodemon -D
Modules utiles
nodemon
Redémarre automatiquement le programme quand les fichiers changent :
npm install nodemon -D
Ajouter dans package.json :
"scripts": {
"start": "nodemon fichier.js"
}
Lancer avec : npm start ou npm run start
dotenv
Pour stocker les informations sensibles hors du code :
npm install dotenv
Fichier .env :
cleAPI="FKSDKLJFKDSKLJF898..."
utilisateurBD="admin"
motDePasse="password"
Utilisation :
import dotenv from 'dotenv';
dotenv.config();
const cleAPI = process.env.cleAPI;
Important : Le fichier
.envdoit être dans le même répertoire que le fichier qui appelledotenv.config(). Utiliserdotenv.config()une seule fois dans le projet (fichier principal).
.gitignore
**/node_modules
.env
Ajouter un fichier .env.exemple avec les clés sans valeurs.
Mise à jour des modules
npm update [-g] [<pkg>...]
Node.js - Express.js
Express.js est le framework standard pour le développement de serveur web en Node.js.
npm install express
Premier serveur web
import express from 'express';
const app = express();
const PORT = 3000;
app.get('/', (req, res) => {
res.send("<h1>Mon premier serveur web sur express !</h1>");
});
app.listen(PORT, () => {
console.log(`Serveur démarré sur le port ${PORT}`);
});
Les objets req et res
req (Request) permet de : - Accéder aux paramètres de l'URL - Accéder aux données du body - Manipuler les cookies
res (Response) permet de : - Retourner des informations au client - Modifier le code de statut - Rediriger une demande
Routes et méthodes HTTP
| Méthode HTTP | Code Express |
|---|---|
| GET | app.get(path, function(req, res) { … }) |
| POST | app.post(path, function(req, res) { … }) |
| PUT | app.put(path, function(req, res) { … }) |
| DELETE | app.delete(path, function(req, res) { … }) |
Node.js - Intergiciel (Middleware)
Un intergiciel est un logiciel exécuté durant la communication entre deux applications. Les middlewares forment des couches concentriques autour de l'application. La requête les traverse un à un.
On utilise app.use() avec une fonction en paramètre. Important : Exécuter next() à la fin de chaque intergiciel pour continuer le traitement.
function historique(req, res, next) {
console.log(`${req.method} ${req.url}`);
next(); // Continue le traitement
}
app.use(historique);
On peut aussi l'appliquer au niveau d'une route :
app.get('/api/liste', historique, (req, res) => { ... });
express.json()
Intergiciel intégré à Express qui transforme le JSON reçu du client en objet JavaScript accessible via req.body :
app.use(express.json());
app.post('/', (req, res) => {
const id = req.body.id;
});
Morgan
Intergiciel pour afficher/journaliser les requêtes traitées :
npm install morgan
import morgan from 'morgan';
app.use(morgan('dev'));
Écrire dans un fichier :
import fs from 'fs';
var accessLogStream = fs.createWriteStream('./access.log', { flags: 'a' });
app.use(morgan('dev', { stream: accessLogStream }));
Node.js - Routage
Routes dynamiques
Paramètre directement dans l'URL avec : :
app.get('/professeurs/:code', (req, res) => {
const code = req.params.code;
res.send(`Professeur avec le code ${code}`);
});
Deux paramètres séparés par / :
app.get('/professeurs/:code_professeur/cours/:code_cours', (req, res) => {
const params = req.params;
// params.code_professeur, params.code_cours
});
Danger : Toujours mettre les routes les plus spécifiques en premier pour éviter les conflits.
Le routage avec Router
Le module Router permet de regrouper des routes similaires dans un fichier séparé.
// professeurs.route.js
import express from 'express';
const router = express.Router();
router.get('/:code_professeur/cours/:code_cours', (req, res) => { ... });
router.get('/:code_professeur', (req, res) => { ... });
export default router;
// index.js
import professeursRouter from './professeurs.route.js';
app.use('/api/professeurs', professeursRouter);
Toutes les routes commençant par /api/professeurs seront dirigées vers le fichier de routage.
Node.js - Structure d'un projet Express
Structure recommandée avec séparation : Modèle (BD), Route (endpoints), Contrôleur (logique applicative).
/projet
├── .env
├── package.json
├── index.js
├── /node_modules
└── /src
├── /config (connexion BD: db.js)
├── /controllers (nomRessource.controller.js)
├── /models (nomRessource.model.js)
└── /routes (nomRessource.route.js)
Exemple contrôleur
// controllers/produits.controller.js
import { produits } from '../models/produits.model.js';
export const getProduits = (req, res) => {
res.json(produits);
};
export const createProduit = (req, res) => {
// Logique pour créer un produit
};
Exemple route
// routes/produits.route.js
import express from 'express';
import { getProduits, createProduit } from '../controllers/produits.controller.js';
const router = express.Router();
router.get('/', getProduits);
router.post('/', createProduit);
export default router;
Node.js - Asynchronisme en JavaScript
Asynchrone : le fil d'exécution principal n'est pas bloqué en attendant une réponse.
Promesses (Promises)
L'objet Promise représente une valeur qui peut être disponible maintenant, dans le futur ou indiquée comme impossible à fournir.
États d'une Promise : - pending (en attente) - fulfilled (tenue) - rejected (rompue) - settled (acquittée)
Créer une fonction qui retourne une promesse
function maFonction() {
return new Promise((resolve, reject) => {
try {
// traitement...
resolve("Tout va bien");
} catch (error) {
reject(error);
}
});
}
maFonction()
.then((resultat) => { console.log(resultat); })
.catch((erreur) => { console.log(erreur); });
Chaîne de promesses
choisirIngredients()
.then(ingredients => placerLaCommande(ingredients))
.then(commande => ramasserLaCommande(commande))
.then(pizza => mangerLaPizza(pizza))
.catch(gererErreur);
Async / Await
La méthode la plus récente et élégante. On utilise async devant la fonction et await devant l'appel.
async function maFonction() {
return new Promise((resolve, reject) => {
try {
resolve("Tout va bien");
} catch (error) {
reject(error);
}
});
}
try {
const resultat = await maFonction();
console.log(resultat);
} catch (erreur) {
console.log(erreur);
}
console.log('Fin du traitement');
// > Tout va bien
// > Fin du traitement
Avec await, l'exécution attend la résolution de la promesse avant de continuer.
Node.js - MySQL avec Express.js
Installation
npm install mysql2
Création du pool de connexions
Fichier .env :
MYSQL_HOST="localhost"
MYSQL_USER="root"
MYSQL_PASSWORD=""
MYSQL_DATABASE="<nom_bd>"
MYSQL_CONNECTION_LIMIT=10
Le mot de passe par défaut pour root dans Devilbox est une chaîne vide.
// src/config/db.js
import mysql from "mysql2/promise";
import dotenv from "dotenv";
dotenv.config();
const pool = mysql.createPool({
connectionLimit: process.env.MYSQL_CONNECTION_LIMIT,
host: process.env.MYSQL_HOST,
user: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD,
database: process.env.MYSQL_DATABASE
});
export default pool;
Utilisation
import db from './config/db.js';
const requete = `SELECT nom, prenom FROM professeurs WHERE id = ?`;
const params = [1];
try {
// mysql2/promise retourne [résultats, infos_champs]
const [resultats] = await db.query(requete, params);
console.log(resultats);
} catch (erreur) {
console.log(`Erreur ${erreur.sqlState} : ${erreur.sqlMessage}`);
}
resultatsest toujours un tableau d'objets- Accéder à une colonne :
resultats[0].prenom - En cas d'erreur :
erreur.sqlStateeterreur.sqlMessage
Exemple complet (Modèle / Contrôleur / Route)
Modèle (professeurs.model.js) :
import pool from '../config/db.js';
const getProfesseur = async (id) => {
const requete = `SELECT nom, prenom FROM professeurs WHERE id = ? LIMIT 1`;
const params = [id];
try {
const [resultats] = await pool.query(requete, params);
return resultats[0] ?? null;
} catch (erreur) {
throw erreur;
}
};
export default { getProfesseur };
Contrôleur (professeurs.controller.js) :
import professeursModel from "../models/professeurs.model.js";
const trouverUnProfesseur = async (req, res) => {
if (!req.params.id || parseInt(req.params.id) <= 0) {
return res.status(400).send({ message: "L'id est obligatoire et > 0" });
}
try {
const professeur = await professeursModel.getProfesseur(req.params.id);
// Gérer le cas 404 si professeur est null
res.send(professeur);
} catch (erreur) {
res.status(500).send({ message: "Erreur lors de la récupération" });
}
};
export { trouverUnProfesseur };
Route (professeurs.route.js) :
import { trouverUnProfesseur } from '../controllers/professeurs.controller.js';
import express from 'express';
const router = express.Router();
router.get('/:id', trouverUnProfesseur);
export default router;
Déploiement - Render
Render est une plate-forme d'hébergement cloud pour déployer des applications web, APIs et bases de données PostgreSQL.
Caractéristiques : interface conviviale, bases de données intégrées, déploiement automatisé depuis GitHub, domaines personnalisés et SSL.
Attention : Plan gratuit = une seule BD, durée de vie de 30 jours. Long délai après inactivité.
Étapes
- Créer un compte sur https://render.com/ (plan gratuit, connexion avec GitHub)
- Créer la base de données : Dashboard → Create New PostgreSQL → Nom + région Virginie + plan gratuit
- Connexion depuis DBeaver : Utiliser la valeur
External Database URL, extraire les infos après l'arobase. Type de connexion :jdbc:postgresql://... - Créer le service web : New Service → Web Service → Connecter le repo GitHub
- Ajuster la Start command pour le fichier de démarrage
- Plan gratuit
- Ajouter les variables d'environnement du fichier
.env
- Déployer → L'URL de l'API est affichée en haut de la page
Le déploiement est automatique : chaque push sur GitHub redéploie.
Déploiement - PostgreSQL
PostgreSQL et Devilbox
Devilbox lance une instance PostgreSQL (pgsql-1) au démarrage, accessible sur le port 5432 en localhost. Utilisateur par défaut : postgres, mot de passe vide, BD : postgres.
Migration MySQL → PostgreSQL
Principales différences :
- Champ auto-increment → type SERIAL
- Concept de schemas (public par défaut)
DROP TABLE IF EXISTS public.pokemon;
CREATE TABLE public.pokemon (
id SERIAL PRIMARY KEY,
nom VARCHAR(50),
type_primaire VARCHAR(50),
type_secondaire VARCHAR(50),
pv INTEGER,
attaque INTEGER,
defense INTEGER
);
Connexion depuis Express.js
Fichier .env :
PG_USER="postgres"
PG_HOST="localhost"
PG_DATABASE="postgres"
PG_PASSWORD=""
PG_PORT=5432
npm install pg
// src/config/db_pg.js
import pg from 'pg';
import dotenv from 'dotenv';
dotenv.config();
let params = {
user: process.env.PG_USER,
host: process.env.PG_HOST,
database: process.env.PG_DATABASE,
password: process.env.PG_PASSWORD,
port: process.env.PG_PORT
};
// SSL pour Render (ajouter PG_SSL=true dans .env)
if (process.env.PG_SSL) {
params.ssl = { rejectUnauthorized: false };
}
const pool = new pg.Pool(params);
export default pool;
Requêtes préparées
Avec PostgreSQL, utiliser $1, $2, etc. au lieu de ? :
const requete = 'SELECT id, nom FROM pokemon WHERE type_primaire LIKE $1';
// INSERT avec RETURNING pour récupérer l'id :
const requete2 = 'INSERT INTO pokemon (nom) VALUES ($1) RETURNING id';
Résultats dans resultat.rows au lieu de resultat directement :
const resultat = await pool.query(requete, parametres);
return resultat.rows;
En cas d'erreur : erreur.code (au lieu de sqlState) et erreur.message (au lieu de sqlMessage).
Frontend - Fetch API
Fetch est une interface JavaScript native pour faire des requêtes HTTP. Fonctionne de façon asynchrone avec les Promises.
Syntaxe
let reponse = await fetch(url, options);
Récupérer la réponse
let reponse = await fetch(url);
if (reponse.ok) { // Code entre 200 et 299
const data = await reponse.json(); // Convertir en JSON (async)
console.log(data);
} else {
console.log("Erreur HTTP:", reponse.status);
}
Modifier les entêtes
let response = fetch(url, {
headers: {
Authorization: 'cle_api dzIdZzvrPmokuWqkjbcN'
}
});
Envoyer des données dans le body
let pokemon = {
nom: "Pantoufleur",
type_primaire: "Tisane",
pv: 10
};
let reponse = await fetch('http://localhost:3000/api/pokemons', {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify(pokemon)
});
let resultat = await reponse.json();
Frontend - Toolpad Studio
Toolpad Studio est un générateur d'outils internes low-code, open source, alimenté par Material UI.
Note 2026 : Le projet n'est plus supporté par MUI mais reste disponible sur GitHub.
Installation
npm install -g pnpm@latest-10
pnpm dlx create-toolpad-app@latest --studio
pnpm run dev
Utilisation
- Interface : Glisser-déposer des composants (Data Grid, Image, etc.)
- Requêtes : Créer des requêtes HTTP REST API dans la section Queries
- Transform : Transformer les résultats avec du JavaScript
- Lier les données : Utiliser le bouton "Bind" pour lier une propriété à une requête (ex:
dogQuery.data) - Interactivité : Créer des paramètres liés à des sélections (ex:
dataGrid.selection?.['race'] ?? 'akita') - Preview : Prévisualiser l'application finale
Frontend - Variables d'environnement avec Toolpad
- Ajouter un fichier
.envà la racine du projet avec les variables - Dans l'éditeur Toolpad, lors du binding d'un paramètre, aller dans l'onglet
Environment variable - Sélectionner la variable voulue dans la liste
- Utiliser le paramètre dans les expressions JS :
${parameters.base_url}/api/pokemons/liste
Sécurité - Clé API
Une clé d'API est une chaîne de caractères aléatoire unique pour identifier et authentifier un utilisateur. Ce n'est pas de l'information encryptée.
// Générer un UUID v4
const uuid = crypto.randomUUID(); // "3b241101-e2bb-4255-8caf-4136c566a962"
Fonctionnement
- L'utilisateur crée un compte (email + mot de passe)
- Une clé API est générée et stockée en BD ; le mot de passe est hashé
- La clé est retournée à l'utilisateur
- L'utilisateur ajoute la clé dans l'entête
Authorizationde chaque requête - Un intergiciel valide la clé
Ajouter la clé dans l'entête
Dans Postman : Headers → Authorization → cle_api <votre_clé>
Intergiciel d'authentification
// src/middlewares/authentification.middleware.js
import { validationCle } from "../models/utilisateur.model";
const authentification = async (req, res, next) => {
if (!req.headers.authorization) {
return res.status(401).json({ message: "Vous devez fournir une clé api" });
}
const cleApi = req.headers.authorization.split(' ')[1];
try {
const cleValide = await validationCle(cleApi);
if (cleValide) {
next();
} else {
return res.status(401).json({ message: "Clé API invalide" });
}
} catch (err) {
return res.status(500).json({ message: "Erreur de validation" });
}
};
export default authentification;
Protection des routes
Globalement :
app.use(authentification);
Par groupe de routes :
app.use('api/test', authentification, routerTest);
Par route individuelle :
router.post("/", authentification, testController.ajouterItem);
Sécurité - Hashage de mot de passe
Les mots de passe doivent toujours être hashés avant d'être stockés en BD.
BCrypt
npm install bcrypt
Hasher un mot de passe
import bcrypt from 'bcrypt';
const costFactor = 10;
const password = "Admin@1234";
const hash = await bcrypt.hash(password, costFactor);
Structure du hash
Format : $[Algorithme]$[costFactor]$[Salt/Hash] (60 caractères)
Exemple : $2b$10$MUWYJRZ4mnHGfzf1Dy.34.1cXsQfJ9d3Me1YmUYWY90F1xXnthK2q
2b: Algorithme BCrypt10: Cost factor- 22 caractères : Salt
- 31 caractères : Hash
Comparer un mot de passe
const motDePasseValide = await bcrypt.compare(password, hash);
if (motDePasseValide) {
// Valide
} else {
// Incorrect
}
Sécurité - JWT
JSON Web Token (JWT) est un standard pour l'authentification. La page de notes de cours est en construction et contient principalement des liens de référence :
- https://auth0.com/docs/secure/tokens/json-web-tokens
- https://jwt.io/
- https://fr.wikipedia.org/wiki/JSON_Web_Token
Un exercice est prévu pour implémenter une protection par JWT avec un client créé via Vite (Vanilla JS + Axios).
Sécurité - Authentification avec JWT
Page en construction. Contient les sujets prévus :
Clé API vs JWT
- Référence : https://www.algolia.com/blog/engineering/api-keys-vs-json-web-tokens/
Fonctionnement
- Références sur l'implémentation JWT avec Express.js :
- https://www.geeksforgeeks.org/how-to-implement-jwt-authentication-in-express-js-app/
- https://www.npmjs.com/package/jsonwebtoken
Aide - Démarrer un projet Express.js
Checklist pour chaque nouveau projet :
- Vérifier Node.js :
node -v - Créer un nouveau répertoire pour le projet
- Initialiser :
npm init -y - Créer le fichier de démarrage (
index.js) à la racine - Installer nodemon :
npm install nodemon -D - Ajouter le script dans
package.json:"start": "nodemon index.js" - Installer les modules :
npm install express dotenv morgan - Ajouter
console.log("Je suis prêt à commencer");dansindex.js - Tester :
npm start
Aide - Récupérer les paramètres d'une requête
Paramètre de la section query
URL : http://localhost:3000/api/users?code=1&nom=Mathieu
app.get('/api/users', (req, res) => {
const queryParams = req.query;
console.log('code :', queryParams.code);
console.log('Nom :', req.query.nom);
if (!queryParams.code) {
res.status(404).send('Le code est obligatoire');
return;
}
res.send(`Code ${queryParams.code}`);
});
Paramètre dans la route
URL : http://localhost:3000/api/users/1
app.get('/api/users/:code', (req, res) => {
const params = req.params;
console.log('code :', params.code);
});
Paramètre dans le corps (body)
Nécessite le middleware express.json().
app.use(express.json());
app.post('/api/users', (req, res) => {
const utilisateur = req.body;
if (!utilisateur.prenom || !utilisateur.nom) {
res.status(400).send('Le nom complet est obligatoire');
return;
}
res.send(`Utilisateur ${utilisateur.prenom} ${utilisateur.nom} créé`);
});
Aide - Erreur avec CORS
CORS (Cross-Origin Resource Sharing) : les requêtes HTTP multi-origine depuis les scripts sont restreintes pour la sécurité.
Solution simple — permettre toutes les requêtes multi-origines :
npm i cors
import cors from 'cors';
app.use(cors());
Pour plus de finesse : https://expressjs.com/en/resources/middleware/cors.html
Exercices
Exercice 01 - Utilisation d'un service web
Utiliser Postman pour interroger plusieurs APIs publiques : - Random Cat Fact API - Joke API - Chucknorris.io - Random User - ImgFlip
Créer une collection Postman "Services Web H2026" avec un dossier "Exercice 01".
Exercice 02 - Serveur Web en Node.js
Créer un serveur web en Node.js pur (sans Express) avec gestion de routes et paramètres.
Exercice 03 - Hello World API
Créer une première API avec Express.js qui retourne des salutations.
Exercice 03b - MySQL et Express.js
Compléter l'exemple de la note de cours MySQL+Express en gérant le cas 404 (professeur non trouvé). Créer la BD, la table, la structure de projet, installer les dépendances, tester avec Postman.
Exercice 04 - Pokemon API
Créer une API complète pour gérer des Pokémons avec une base de données MySQL, en suivant la structure Modèle/Contrôleur/Route.
Exercice 04b - Documentation
Documenter l'API Pokemon avec OpenAPI/Swagger.
Exercice 05 - Clé API
Ajouter un système de protection par clé API à l'API Pokemon.
Exercice 06 - Déploiement sur Render
Déployer l'API sur Render avec une base de données PostgreSQL.
Exercice 07 - Consommer un API avec Fetch
Créer une page HTML qui consomme l'API déployée en utilisant Fetch.
Exercice 08 - Toolpad Studio
Créer une application avec Toolpad Studio qui consomme l'API.
Exercice 09 - CRUD avec Toolpad Studio
Créer une application Toolpad Studio complète avec opérations CRUD.
Exercice 10 - Générateur de Meme
Créer un générateur de memes en utilisant l'API ImgFlip.
Formatif formel
Exercice formatif pour préparer les examens.
Examen 2 - Révision A et B
Exercices de révision pour le deuxième examen.
Source : https://services-web-victo.github.io/ — Cours Services Web, Cégep de Victoriaville, Hiver 2026 © 2025 par Mathieu Fréchette — Licence CC BY-NC-SA