Skip to content

3. Chapitre 2 - Installation du serveur [NestJS]

Le client [Flutter] de ce document a besoin d’un serveur à interroger : le serveur [NestJS] de l’application [RdvMedecins], livré avec ce document dans le dossier [rdvmedecins-nestjs-server]. Ce document étant consacré à [Flutter], ce serveur y est traité comme une « boîte noire » : ce chapitre explique comment l’installer, le configurer, créer sa base de données [MySQL], le lancer et le tester, mais n’en étudie pas le code. Le lecteur qui souhaite apprendre [NestJS] pourra lire le document Introduction au langage TypeScript et au framework NestJS (2026).

Ce chapitre reprend, pour l’essentiel, le chapitre « Installation d’un serveur NestJS » du document Introduction au langage Dart par l’exemple (2026), adapté à un autre serveur : le serveur de calcul de l’impôt de ce document est ici remplacé par le serveur [RdvMedecins], et sa base de données [dbimpots-2019] par la base [dbrdvmedecins].

3.1. Installation de Laragon

Pour créer la base de données [MySQL] utilisée par le serveur [NestJS], nous allons utiliser l’outil Laragon. En septembre 2026, Laragon est disponible à l’URL [Download Laragon – Fast & Modern Dev Environment ]. Laragon amène avec lui des outils qui permettent d’installer un serveur PHP et le SGBD [MySQL] :

Image

  • en [1], un serveur web Apache ;
  • en [2], le SGBD [MySQL] ;
  • en [3], un outil de messagerie que nous n’utiliserons pas ;
  • en [4], lance tous les services (Apache, [MySQL], MailPit) ;
  • en [5], affiche la page web d’URL [http://localhost]. Par défaut, cette URL désigne le dossier <laragon-install>\www où <laragon-install> est le dossier d’installation de Laragon ;
  • en [6], lance l’Outil HeidiSQL qui permet de gérer des bases de données MySQL. C’est avec cet outil que nous créerons la base de données utilisée par le serveur [RdvMedecins] ;
  • en [7], ouvre un terminal dans lequel tous les binaires utiles à Laragon sont dans le PATH ;
  • en [8], ouvre un explorateur windows dans le dossier associé à l’URL [http://localhost] ;

Le dossier d’installation de Laragon comporte les éléments suivants :

 
  • En [1], le dossier [bin] contient de nombreuses applications :
  • En [1], le serveur web Apache ;
  • en [2], un terminal de commandes ;
  • en [3], un client [git] permettant de versionner les différentes versions d’un projet ;
  • en [4], un gestionnaire de bases de données ;
  • en [5], un gestionnaire courrier ;
  • en [6], le SGBD [MySQL] ;
  • en [7], un autre serveur web ;
  • en [8], [node.js] pour exécuter des scripts Javascript ;
  • en [9], un éditeur de texte ;
  • en [10], l’interpréteur PHP ;
  • en [11], l’interpréteur [Python] ;
  • en [12], un serveur de cache ;

Laragon ramène avec lui un environnement de développement conséquent. Dans ce cours, nous n’utiliserons que le SGBD [MySQL] [6] et l’outil [heidisql] pour le gérer graphiquement ;

3.2. Installation et configuration du serveur [NestJS]

Le dossier [rdvmedecins-nestjs-server] a l’arborescence suivante :

rdvmedecins-nestjs-server/
├── package.json                  # dépendances et scripts npm
├── tsconfig.json                 # configuration du compilateur TypeScript
├── nest-cli.json                 # configuration du CLI NestJS
├── .env.example                  # modèle du fichier de configuration .env
├── database/
│   └── dbrdvmedecins.sql         # script de création de la base de données
└── src/
    ├── main.ts                   # point d'entrée du serveur
    ├── app.module.ts             # module racine
    ├── config/                   # connexion à la base de données
    ├── entities/                 # entités TypeORM (une classe par table)
    ├── domain/                   # objets métier
    ├── repositories/             # accès aux données (couche DAO)
    ├── metier/                   # couche métier
    ├── web/                      # contrôleur JSON et modèles de réponse
    └── auth/                     # authentification JWT et rôles

Chaque sous-dossier de [src/] contient son propre [README.md], qui résume le rôle des fichiers qu’il contient. Le code source de ces fichiers n’est pas étudié dans ce document.

Dans le dossier [rdvmedecins-nestjs-server], tapez les commandes suivantes dans un terminal :

npm install
cp .env.example .env

Le fichier [.env] fixe des variables d’environnement :

# -----------------------------------------------------------------------
# Fichier d'exemple de configuration.
# Copier ce fichier en [.env] puis adapter les valeurs à votre machine.
# Le fichier [.env] ne doit jamais être partagé ou commité (mots de passe).
# -----------------------------------------------------------------------

# port d'écoute du serveur NestJS (le serveur Spring original écoutait sur 8080)
PORT=8080

# paramètres de connexion à la base de données MySQL [dbrdvmedecins]
DB_HOST=localhost
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=
DB_DATABASE=dbrdvmedecins

# en développement uniquement : TypeORM peut créer/mettre à jour automatiquement
# les tables à partir des entités. On le désactive ici car nous fournissons notre
# propre script SQL [database/dbrdvmedecins.sql], comme le faisait le projet Spring original.
DB_SYNCHRONIZE=false

# -----------------------------------------------------------------------
# authentification (cf. src/auth/)
# -----------------------------------------------------------------------
# clé secrète utilisée pour signer/vérifier les jetons JWT - à changer
# impérativement en production (une longue chaîne aléatoire, jamais commitée).
JWT_SECRET=change-moi-en-une-longue-chaine-aleatoire
# durée de validité d'un jeton JWT (cf. la documentation de la librairie [ms] :
# "60", "2h", "7d"...)
JWT_EXPIRES_IN=2h

Adaptez le fichier [.env] fraîchement créé à votre installation [MySQL] (utilisateur, mot de passe, port). Ce fichier contient aussi, depuis l’ajout de l’authentification, une clé JWT_SECRET : la valeur d’exemple convient pour suivre ce document, mais devra impérativement être remplacée par une chaîne aléatoire propre à vous avant tout usage réel.

3.3. Création de la base de données

La base de données [dbrdvmedecins] reprend, dans sa structure, celle du document original (2014, Spring 4 / [MySQL] 5) : mêmes tables [medecins], [clients], [creneaux], [rv], mêmes clés étrangères, même contrainte d’unicité. Seule nouveauté, apportée par l’étape « authentification » de ce portage : la table [users]. Les relations entre ces tables sont les suivantes : un médecin ([medecins]) possède plusieurs créneaux horaires ([creneaux], clé étrangère [id_medecin]) ; un rendez-vous ([rv]) associe un jour, un créneau (donc un médecin) et un client ([clients]) ; la table [users] est indépendante des quatre autres : elle ne sert qu’à l’authentification.

3.3.1. [database/dbrdvmedecins.sql]

Image

  -- =========================================================================
  -- Base de données [dbrdvmedecins] - MySQL 8
  --
  -- Ce script reprend EXACTEMENT le schéma relationnel du document original
  -- (2014, Spring 4 / Spring Data / MySQL 5) : mêmes tables, mêmes colonnes,
  -- mêmes clés étrangères, même contrainte d'unicité. Seul le moteur de base
  -- de données change de nom (MySQL 5 -> MySQL 8), pas la structure.
  --
  -- Le document original utilisait TROIS tables (users, roles,
 -- users_roles) pour permettre à un utilisateur d'avoir PLUSIEURS rôles.
 -- Puisque cette application ne connaît que deux rôles mutuellement
 -- exclusifs (ADMIN, USER), nous simplifions ici à une seule colonne [role]
 -- sur la table [users] plutôt que de reconstruire la relation plusieurs-à-
 -- plusieurs d'origine - une simplification pédagogique assumée, documentée
 -- dans le chapitre "Authentification" du cours.
 -- =========================================================================

 -- on repart d'une base vide à chaque exécution du script (pratique en développement)
 DROP TABLE IF EXISTS rv;
 DROP TABLE IF EXISTS creneaux;
 DROP TABLE IF EXISTS clients;
 DROP TABLE IF EXISTS medecins;
 DROP TABLE IF EXISTS users;

 CREATE DATABASE IF NOT EXISTS dbrdvmedecins CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
 USE dbrdvmedecins;

 CREATE TABLE medecins (
   id      INT AUTO_INCREMENT PRIMARY KEY,
   version INT NOT NULL DEFAULT 0,
   titre   VARCHAR(5)  NOT NULL,
   nom     VARCHAR(20) NOT NULL,
   prenom  VARCHAR(20) NOT NULL
 );

 CREATE TABLE clients (
   id      INT AUTO_INCREMENT PRIMARY KEY,
   version INT NOT NULL DEFAULT 0,
   titre   VARCHAR(5)  NOT NULL,
   nom     VARCHAR(20) NOT NULL,
   prenom  VARCHAR(20) NOT NULL
 );

 CREATE TABLE creneaux (
   id         INT AUTO_INCREMENT PRIMARY KEY,
   version    INT NOT NULL DEFAULT 0,
   id_medecin INT NOT NULL,
   hdebut     INT NOT NULL,
   mdebut     INT NOT NULL,
   hfin       INT NOT NULL,
   mfin       INT NOT NULL,
   CONSTRAINT fk_creneaux_medecin FOREIGN KEY (id_medecin) REFERENCES medecins(id)
 );

 CREATE TABLE rv (
   id         INT AUTO_INCREMENT PRIMARY KEY,
   version    INT NOT NULL DEFAULT 0,
   jour       DATE NOT NULL,
   id_creneau INT NOT NULL,
   id_client  INT NOT NULL,
   CONSTRAINT fk_rv_creneau FOREIGN KEY (id_creneau) REFERENCES creneaux(id),
   CONSTRAINT fk_rv_client  FOREIGN KEY (id_client)  REFERENCES clients(id),
   CONSTRAINT unq1_rv UNIQUE (jour, id_creneau)
 );

 CREATE TABLE users (
   id       INT AUTO_INCREMENT PRIMARY KEY,
   version  INT NOT NULL DEFAULT 0,
   login    VARCHAR(30) NOT NULL UNIQUE,
   password VARCHAR(100) NOT NULL,
   nom      VARCHAR(40) NOT NULL,
   role     VARCHAR(10) NOT NULL
 );

 INSERT INTO medecins (titre, nom, prenom) VALUES
   ('Mme', 'PELISSIER', 'Marie'), ('Mr', 'TATOU', 'Paul'), ('Melle','DUPONT', 'Sophie');

 INSERT INTO clients (titre, nom, prenom) VALUES
   ('Mr', 'MARTIN', 'Jean'), ('Mme', 'DURAND', 'Alice'),
   ('Melle','BISTROU', 'Brigitte'), ('Mr', 'GERMAN', 'Luc');

 INSERT INTO creneaux (id_medecin, hdebut, mdebut, hfin, mfin) VALUES
   (1, 8, 0, 8, 20), (1, 8, 20, 8, 40), /* ... */ (1, 11, 40, 12, 0);

 INSERT INTO creneaux (id_medecin, hdebut, mdebut, hfin, mfin) VALUES
   (2, 14, 0, 14, 20), (2, 14, 20, 14, 40), /* ... */ (2, 17, 40, 18, 0);

 INSERT INTO rv (jour, id_creneau, id_client) VALUES (CURDATE(), 2, 3);

 INSERT INTO users (login, password, nom, role) VALUES
   ('admin', '$2b$10$YSNBJXXHX9mp13nPZb6YGu8M1jR6uvPwO1s2X73z0QAqBR7TOQjoq', 'Administrateur', 'ADMIN'),
   ('user',  '$2b$10$6Cvoion7AgJkpYHw5N0jMuWPf8T90J28En56kWBL9PT0bXt7lwW7.', 'Utilisateur',    'USER');

Commentons ce code :

  • lignes 20-24 : [DROP TABLE IF EXISTS …] — le script repart d’une base vide à chaque exécution, dans l’ordre inverse des dépendances (rv d’abord, qui référence [creneaux] et clients, jusqu’à users, qui n’est référencée par personne) : [MySQL] refuserait de supprimer une table encore référencée par une clé étrangère ;
  • ligne 48 : [id_medecin INT NOT NULL] puis ligne 53 : [CONSTRAINT fk_creneaux_medecin FOREIGN KEY …] — un créneau appartient toujours à un médecin ;
  • ligne 64 : [CONSTRAINT unq1_rv UNIQUE (jour, id_creneau)] — la règle de gestion centrale de l’application : un même créneau, un même jour, ne peut être réservé qu’une seule fois. C’est cette contrainte, et elle seule, qui empêche un double rendez-vous - ni le contrôleur ni la couche métier ne la revérifient explicitement ;
  • lignes 67-74 : [CREATE TABLE users (…)] — la table ajoutée par cette étape. login est UNIQUE (deux utilisateurs ne peuvent pas partager le même identifiant de connexion) ; password stocke un mot de passe haché par bcrypt (jamais en clair) ; role vaut 'ADMIN' ou 'USER' - une simple colonne texte, là où le document original utilisait trois tables (users, roles, users_roles) pour autoriser plusieurs rôles par utilisateur. Cette application n’ayant besoin que de deux rôles mutuellement exclusifs, la relation plusieurs-à-plusieurs d’origine seraît allé au-delà du nécessaire ;
  • lignes 91-93 : [INSERT INTO users …] — deux comptes de démonstration, admin/admin (rôle ADMIN) et user/user (rôle USER), avec leurs mots de passe déjà hachés par bcrypt (10 tours de salage) - jamais en clair, y compris dans ce script de démonstration.

3.3.2. Exécution du script avec HeidiSQL

Pour créer la base de données [MySQL] nécessaire au serveur [NestJS], nous procédons de la façon suivante :

 
  • En [1], tous les services doivent être lancés ;
  • en [2], l’option [Database] vous permet de gérer la base de données ;

L’option [Database] lance un utilitaire appelé HeidiSQL :

  • En [1], vous pouvez sélectionner divers SGBD. La valeur par défaut proposée est le SGBD MySQL. Cela convient ;
  • en [2], vous ouvrez une session de HeidiSQL ;
  • En [1-2], exécutez un fichier SQL puis choisissez le fichier [dbrdvmedecins.sql] que vous trouverez dans le dossier [rdvmedecins-nestjs-server/database] :

Image

L’exécution du fichier [3] va créer la base [dbrdvmedecins] utilisée par le serveur [NestJS]. Pour le voir, faites F5 dans HeidiSQL pour rafraîchir l’affichage :

Image

La base de données [dbrdvmedecins] a été créée. On peut en voir le contenu :

Image

  • en [1-3], la table [clients] ;
    • [id] est la clé primaire de la table ;
    • [version] est le numéro de version de la ligne. Ce numéro augmente à chaque modification de la ligne ;
    • [titre, nom, prenom] identifient la personne ;

Image

  • en [1-3], la table [medecins] ;
    • [id] est la clé primaire de la table ;
    • [version] est le numéro de version de la ligne ;
    • [titre, nom, prenom] identifient la personne ;

Image

  • en [1-3], la table des créneaux horaires de consultation des médecins :
    • [id] : clé primaire ;
    • [version] : numéro de version de la ligne ;
    • [id-medecin] : clé étrangère ciblée sur la clé primaire du médecin ;
    • [hdebut, mdebut] : heure et minutes de début de la consultation. Ainsi [8,20] signifie 8h20.
    • [hfin, mfin] : heure et minutes de fin de la consultation.

Image

  • En [1-3], la table [rv] des rendez-vous pris ;
    • [id] : clé primaire ;
    • [version] : numéro de version ;
    • [jour] : jour du rendez-vous ;
    • [id_creneau] : clé étrangère vers la clé primaire du créneau horaire du rendez-vous. Comme un créneau horaire est lié à un médecin, on sait quel médecin assure ce rendez-vous ;
    • [id_client] : clé étrangère vers le numéro (clé primaire) du client qui consulte ;

Image

  • en [1-3], la table [users] qui liste les utilisateurs autorisés à utiliser l’application [NestJS] ;
    • [id, version] : clé primaire, numéro de version ;
    • [login, password] : identifiants de l’utilisateur ;
    • [nom] : nom donné à l’utilisateur ;
    • [role] : son rôle. [ADMIN] a tous les droits. [USER] n’a que des droits de lecture. Il pourra consulter l’agenda d’un médecin mais pas prendre / annuler un rendez-vous ;

3.4. Lancement du serveur [NestJS]

Lancez tous les services de Laragon [Start All], si ce n’est déjà fait. Puis démarrez le serveur NestJS dans un terminal VSCode du projet NestJS :

npm run start:dev

La console affiche, dans l’ordre : le démarrage du module [NestJS], la liste des routes exposées par le contrôleur, puis la confirmation du démarrage :

[Nest] RoutesResolver AuthController {/}:
[Nest] RouterExplorer Mapped {/login, POST} route
[Nest] RoutesResolver RdvMedecinsController {/}:
[Nest] RouterExplorer Mapped {/getAllMedecins, GET} route
[Nest] RouterExplorer Mapped {/getAllClients, GET} route
[Nest] RouterExplorer Mapped {/getAllCreneaux/:idMedecin, GET} route
[Nest] RouterExplorer Mapped {/getRvMedecinJour/:idMedecin/:jour, GET} route
[Nest] RouterExplorer Mapped {/getAgendaMedecinJour/:idMedecin/:jour, GET} route
[Nest] RouterExplorer Mapped {/getMedecinById/:id, GET} route
[Nest] RouterExplorer Mapped {/getClientById/:id, GET} route
[Nest] RouterExplorer Mapped {/getCreneauById/:id, GET} route
[Nest] RouterExplorer Mapped {/getRvById/:id, GET} route
[Nest] RouterExplorer Mapped {/ajouterRv, POST} route
[Nest] RouterExplorer Mapped {/supprimerRv, POST} route
[Nest] NestApplication Nest application successfully started
[RdvMedecins] serveur NestJS démarré sur http://localhost:8080

Ce relevé de console confirme que le serveur écoute bien sur le port 8080 - exactement le même port que le serveur Spring Boot du document original, choisi volontairement pour cette raison.

Ces routes constituent l’API JSON que le client [Flutter] consommera au chapitre 4. [POST /login] est la seule route publique : elle renvoie un jeton JWT que toutes les autres routes exigent dans l’entête [Authorization: Bearer <jeton>]. Les routes de consultation ([GET …]) sont accessibles aux rôles ADMIN et USER ; les deux routes qui modifient des données, [POST /ajouterRv] et [POST /supprimerRv], sont réservées au rôle ADMIN. Les réponses métier sont enveloppées dans un objet [{ status, data }] où [status] vaut 0 en cas de succès.

3.5. Tester le serveur avec un navigateur

Depuis l’ajout de l’authentification, toutes les routes de [RdvMedecinsController] exigent un jeton JWT valide : un navigateur, seul, ne permet donc plus de les tester directement. Par exemple :

http://localhost:8080/getAllMedecins

renvoie désormais, sans jeton, une erreur 401 Unauthorized :

Image

C’est le comportement attendu : cette réponse confirme que le serveur protège bien ses routes. Postman (section suivante) est nécessaire pour obtenir un jeton, puis pour l’utiliser.

3.6. Tester le serveur [NestJS] avec Postman

L’outil Postman est disponible à l’URL [https://www.postman.com/downloads/]. Vous aurez peut-être à créer un compte.

Lancez tous les services de Laragon, puis ouvrez l’application de bureau Postman :

  • En [1-2], créez une requête HTTP pour interroger le serveur de gestion des rendez-vous des médecins ;

3.6.1. Se connecter et obtenir un jeton

  1. créer une nouvelle requête Postman, méthode POST, URL http://localhost:8080/login ;
  2. dans l’onglet [Body], choisir raw puis le format JSON, et saisir : {"login": "admin", "password": "admin"} (ou user/user pour tester le rôle USER) ;
  3. envoyer la requête.

Image

  • en [1-2], la requête POST au serveur [NestJS] qui oeuvre sur le port 8080 ;
  • en [3-5], le corps de la requête. Celle-ci transmet au serveur la chaîne JSON des identifiants de l’utilisateur qui veut se connecter, ici admin/admin ;
  • en [6], la réponse du serveur ;
  • en [7], le jeton d’authentification que les requêtes ultérieures doivent envoyer au serveur pour dire “je me suis déjà authentifié” ;

Copiez la valeur de accessToken : c’est ce jeton qu’il faut désormais présenter à chaque requête suivante. Ci-dessus le jeton à copier est “eyJ...zoM” (sans les “).

3.6.2. Appeler une route protégée avec le jeton

Pour GET /getAllMedecins (ou toute autre route de RdvMedecinsController), ajouter dans Postman, onglet Authorization, le type Bearer Token, et coller le jeton copié ci-dessus (Postman ajoute alors lui-même l’entête Authorization: Bearer <jeton>).

Image

  • en [1-2], la requête envoyée au serveur. Elle demande la liste des médecins en base de données ;
  • en [3-5], la requête doit envoyer le jeton d’authentification. En [4], choisissez [Bearer Token] et en [5] collez le jeton d’authentification que vous avez copié précédemment ;
  • en [7], la réponse du serveur ;

3.6.3. Tester les routes POST (/ajouterRv, /supprimerRv)

Même démarche que ci-dessus (méthode POST, corps JSON, jeton en Bearer Token), exactement comme le document original utilisait le complément Chrome « Advanced Rest [Client] » pour les mêmes routes.

Note : vous pouvez retrouver les requêtes Postman qui suivent dans la collection [rdvmedecins-nestJS.postman_collection.json] :

Image

Dans Postman, faire Ctrl-0 pour charger la collection :

Image

  • cliquez sur [1] pour charger la collection et sélectionnez le fichier de la collection. Une fois chargée la collection, celle-ci expose ses requêtes :

Image

Pour tester /ajouterRv avec le jeton d’un compte ADMIN :

  1. requête Postman, méthode POST, URL http://localhost:8080/ajouterRv ;
  2. onglet Authorization : Bearer Token, jeton de admin ;
  3. onglet [Body], raw, JSON : {"jour": "2026-09-13", "idClient": 1, "idCreneau": 5} ;
  4. envoyer la requête.

Image

  • en [3-5], le corps de la requête ;

Image

Vous pouvez vérifier avec HeidiSQL qu’un rendez-vous a été ajouté en base :

Image

Pour tester /supprimerRv, la même démarche s’applique avec le corps {"idRv": 2} (l’identifiant du rendez-vous à annuler (8) ci-dessus).

Image

  • en [4], ci-dessus le numéro du rendez-vous à supprimer ;

Image

  • en [5-7], le jeton d’authentification ;
  • en [9], la réponse du serveur. [status=0] signifie que l’opération s’est bien déroulée. Vous pouvez le vérifier avec HeidiSQL :

Image

  • en [3], le rendez-vous n° 2 n’existe plus ;

À essayer : refaire cette même requête /ajouterRv avec le jeton du compte USER (user/user) plutôt que celui de admin : la réponse devient 403 Forbidden, avec le message le rôle [USER] ne permet pas cette action (rôle requis : ADMIN) - la démonstration concrète du contrôle d’accès par rôle mis en place par le serveur.

3.7. Lancer le client Flutter pour la première fois

Dans le dossier [rdvmedecins_flutter_client] (le serveur [NestJS] devant être démarré au préalable) :

flutter devices          # liste les cibles disponibles sur cette machine
flutter run -d chrome    # web - la cible la plus simple pour un premier essai

Image

L'écran de connexion s'affiche en premier, exactement comme pour les clients précédents de ce cours (cf. chapitre 4) : deux comptes de démonstration sont fournis avec la base de données du serveur, [admin]/[admin] (rôle [ADMIN], accès complet) et [user]/[user] (rôle [USER], lecture seule). Deux boutons « FR »/« EN », en haut à droite, permettent de basculer l'interface en anglais, y compris avant toute connexion. Le chapitre 5 détaille les commandes équivalentes pour le bureau et le mobile, ainsi que le seul réglage propre à ces cibles (l'adresse du serveur, cf. plus loin ce chapitre).