Notes cours services web

Services Web - Notes de cours complètes

Cégep de Victoriaville - Hiver 2026

Enseignants : Mathieu Fréchette & Sébastien Trottier


TABLE DES MATIÈRES

  1. Environnement de développement
  2. API - Introduction
  3. API - Bonnes pratiques
  4. API - Le format JSON
  5. API - Documentation avec OpenAPI
  6. API - Validation et génération de code
  7. Node.js - Introduction
  8. Node.js - Serveur Web
  9. Node.js - NPM
  10. Node.js - Express.js
  11. Node.js - Intergiciel (Middleware)
  12. Node.js - Routage
  13. Node.js - Structure d'un projet Express
  14. Node.js - Asynchronisme en JavaScript
  15. Node.js - MySQL avec Express.js
  16. Déploiement - Render
  17. Déploiement - PostgreSQL
  18. Frontend - Fetch API
  19. Frontend - Toolpad Studio
  20. Frontend - Variables d'environnement avec Toolpad
  21. Sécurité - Clé API
  22. Sécurité - Hashage de mot de passe
  23. Sécurité - JWT
  24. Sécurité - Authentification avec JWT
  25. Aide - Démarrer un projet Express.js
  26. Aide - Récupérer les paramètres d'une requête
  27. Aide - Erreur avec CORS
  28. 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 : true ou false (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 http de 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_modules en partage (ajouter au .gitignore)
  • npm install (sans nom de module) installe tous les modules manquants selon package.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 .env doit être dans le même répertoire que le fichier qui appelle dotenv.config(). Utiliser dotenv.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}`);
}
  • resultats est toujours un tableau d'objets
  • Accéder à une colonne : resultats[0].prenom
  • En cas d'erreur : erreur.sqlState et erreur.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

  1. Créer un compte sur https://render.com/ (plan gratuit, connexion avec GitHub)
  2. Créer la base de données : Dashboard → Create New PostgreSQL → Nom + région Virginie + plan gratuit
  3. Connexion depuis DBeaver : Utiliser la valeur External Database URL, extraire les infos après l'arobase. Type de connexion : jdbc:postgresql://...
  4. 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
  5. 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

  1. Interface : Glisser-déposer des composants (Data Grid, Image, etc.)
  2. Requêtes : Créer des requêtes HTTP REST API dans la section Queries
  3. Transform : Transformer les résultats avec du JavaScript
  4. Lier les données : Utiliser le bouton "Bind" pour lier une propriété à une requête (ex: dogQuery.data)
  5. Interactivité : Créer des paramètres liés à des sélections (ex: dataGrid.selection?.['race'] ?? 'akita')
  6. Preview : Prévisualiser l'application finale

Frontend - Variables d'environnement avec Toolpad

  1. Ajouter un fichier .env à la racine du projet avec les variables
  2. Dans l'éditeur Toolpad, lors du binding d'un paramètre, aller dans l'onglet Environment variable
  3. Sélectionner la variable voulue dans la liste
  4. 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

  1. L'utilisateur crée un compte (email + mot de passe)
  2. Une clé API est générée et stockée en BD ; le mot de passe est hashé
  3. La clé est retournée à l'utilisateur
  4. L'utilisateur ajoute la clé dans l'entête Authorization de chaque requête
  5. Un intergiciel valide la clé

Ajouter la clé dans l'entête

Dans Postman : Headers → Authorizationcle_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 BCrypt
  • 10 : 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 :

  1. Vérifier Node.js : node -v
  2. Créer un nouveau répertoire pour le projet
  3. Initialiser : npm init -y
  4. Créer le fichier de démarrage (index.js) à la racine
  5. Installer nodemon : npm install nodemon -D
  6. Ajouter le script dans package.json : "start": "nodemon index.js"
  7. Installer les modules : npm install express dotenv morgan
  8. Ajouter console.log("Je suis prêt à commencer"); dans index.js
  9. 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

Partager :
Retourner vers page d'accueil

Prêt à multiplier vos clics par 3 à 5 ?

Rejoignez 50 000+ créateurs qui utilisent Bio.ax pour booster leur présence digitale.

Créer mon Bio.ax gratuit