Skip to content

3. Chapter 2 - Installing the [NestJS] server

The [Flutter] client described in this document requires a server to query: the [NestJS] server for the [RdvMedecins] application, included with this document in the [rdvmedecins-nestjs-server] folder. Since this document focuses on [Flutter], that server is treated as a “black box”: this chapter explains how to install it, configure it, create its [MySQL] database, launch it, and test it, but does not examine its code. Readers who wish to learn [NestJS] can refer to the document *Introduction to the TypeScript Language and the NestJS Framework* (2026).

This chapter is essentially a reprint of the chapter “Installing a NestJS Server” from the document *Introduction to the Dart Language Through Examples* (2026), adapted for a different server: the tax calculation server in that document is replaced here by the [RdvMedecins] server, and its [dbimpots-2019] database by the [dbrdvmedecins] database.

3.1. Installing Laragon

To create the [MySQL] database used by the [NestJS] server, we will use the Laragon tool. As of September 2026, Laragon is available at the URL [Download Laragon – Fast & Modern Dev Environment ]. Laragon includes tools that allow you to install a PHP server and the [MySQL] DBMS:

Image

  • in [1], an Apache web server;
  • in [2], the [MySQL] DBMS;
  • in [3], a messaging tool that we will not be using;
  • [4] starts all services (Apache, [MySQL], MailPit);
  • [5] displays the web page for the URL [http://localhost]. By default, this URL refers to the <laragon-install>\www folder, where <laragon-install> is the Laragon installation folder;
  • in [6], launches the HeidiSQL tool, which allows you to manage MySQL databases. We will use this tool to create the database used by the [RdvMedecins] server;
  • In [7], open a terminal where all the binaries needed for Laragon are located in PATH;
  • In [8], open Windows File Explorer to the folder associated with the URL [http://localhost];

The Laragon installation folder contains the following items:

 
  • In [1], the [bin] folder contains numerous applications:
  • In [1], the Apache web server;
  • in [2], a command-line terminal;
  • in [3], a [git] client for versioning different versions of a project;
  • in [4], a database manager;
  • in [5], a mail manager;
  • in [6], the [MySQL] DBMS;
  • [7], another web server;
  • in [8], [node.js] to run JavaScript scripts;
  • [9], a text editor;
  • [10], the PHP interpreter;
  • [11], the [Python] interpreter;
  • [12], a cache server;

Laragon comes with a comprehensive development environment. In this course, we will use only the [MySQL] DBMS [6] and the [heidisql] tool to manage it graphically;

3.2. Installation and Configuration of the [NestJS] Server

The [rdvmedecins-nestjs-server] folder has the following directory structure:

rdvmedecins-nestjs-server/
├── package.json                  # npm dependencies and scripts
├── tsconfig.json                 # TypeScript compiler configuration
├── nest-cli.json                 # NestJS CLI configuration
├── .env.example                  # template of the .env configuration file
├── database/
│   └── dbrdvmedecins.sql         # database creation script
└── src/
    ├── main.ts                   # server entry point
    ├── app.module.ts             # root module
    ├── config/                   # database connection
    ├── entities/                 # TypeORM entities (one class per table)
    ├── domain/                   # business objects
    ├── repositories/             # data access (DAO layer)
    ├── business/                 # business layer
    ├── web/                      # JSON controller and response models
    └── auth/                     # JWT authentication and roles

Each subfolder of [src/] contains its own [README.md] file, which summarizes the purpose of the files it contains. The source code for these files is not covered in this document.

In the [rdvmedecins-nestjs-server] folder, type the following commands in a terminal:

npm install
cp .env.example .env

The [.env] file sets environment variables:

# -----------------------------------------------------------------------
# Sample configuration file.
# Copy this file to [.env] then adapt the values to your machine.
# The [.env] file must never be shared or committed (passwords).
# -----------------------------------------------------------------------

# listening port of the NestJS server (the original Spring server listened on 8080)
PORT=8080

# connection parameters for the MySQL database [dbrdvmedecins]
DB_HOST=localhost
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=
DB_DATABASE=dbrdvmedecins

# in development only: TypeORM can automatically create/update
# the tables from the entities. It is disabled here because we provide our
# own SQL script [database/dbrdvmedecins.sql], as the original Spring project did.
DB_SYNCHRONIZE=false

# -----------------------------------------------------------------------
# authentication (see src/auth/)
# -----------------------------------------------------------------------
# secret key used to sign/verify JWT tokens - MUST be changed
# in production (a long random string, never committed).
JWT_SECRET=change-me-to-a-long-random-string
# validity of a JWT token (see the documentation of the [ms] library:
# "60", "2h", "7d"...)
JWT_EXPIRES_IN=2h

Adapt the newly created [.env] file to your [MySQL] installation (username, password, port). Since authentication was added, this file also contains a key named JWT_SECRET: the example value is suitable for following along with this document, but must be replaced with a random string unique to you before any actual use.

3.3. Creating the Database

The [dbrdvmedecins] database mirrors the structure of the original document (2014, Spring 4 / [MySQL] 5): same tables—[doctors], [clients], [slots], [appointments], now with English names—same foreign keys, same uniqueness constraint. The only new addition, introduced by the “authentication” step of this port: the table [users]. The relationships between these tables are as follows: a doctor ([doctors]) has several time slots ([slots], foreign key [doctor_id]); an appointment ([appointments]) links a day, a time slot (and thus a doctor), and a client ([clients]); the table [users] is independent of the other four: it is used solely for authentication.

3.3.1. [database/dbrdvmedecins.sql]

Image

  -- =========================================================================
  -- Database [dbrdvmedecins] - MySQL 8
  --
  -- This script reproduces EXACTLY the relational schema of the original document
  -- (2014, Spring 4 / Spring Data / MySQL 5): same tables, same columns,
  -- same foreign keys, same uniqueness constraint. Only the database engine
  -- changes its name (MySQL 5 -> MySQL 8), not the structure.
  --
  -- The original document used THREE tables (users, roles,
 -- users_roles) to allow a user to have SEVERAL roles.
 -- Since this application only knows two mutually exclusive roles
 -- (ADMIN, USER), we simplify here to a single [role] column
 -- on the [users] table rather than rebuilding the original many-to-
 -- many relation - a deliberate teaching simplification, documented
 -- in the "Authentication" chapter of the course.
 -- =========================================================================

 -- we start again from an empty database on each run of the script (handy in development)
 DROP TABLE IF EXISTS appointments;
 DROP TABLE IF EXISTS slots;
 DROP TABLE IF EXISTS clients;
 DROP TABLE IF EXISTS doctors;
 DROP TABLE IF EXISTS users;

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

 CREATE TABLE doctors (
   id         INT AUTO_INCREMENT PRIMARY KEY,
   version    INT NOT NULL DEFAULT 0,
   title      VARCHAR(5)  NOT NULL,
   last_name  VARCHAR(20) NOT NULL,
   first_name VARCHAR(20) NOT NULL
 );

 CREATE TABLE clients (
   id         INT AUTO_INCREMENT PRIMARY KEY,
   version    INT NOT NULL DEFAULT 0,
   title      VARCHAR(5)  NOT NULL,
   last_name  VARCHAR(20) NOT NULL,
   first_name VARCHAR(20) NOT NULL
 );

 CREATE TABLE slots (
   id           INT AUTO_INCREMENT PRIMARY KEY,
   version      INT NOT NULL DEFAULT 0,
   doctor_id    INT NOT NULL,
   start_hour   INT NOT NULL,
   start_minute INT NOT NULL,
   end_hour     INT NOT NULL,
   end_minute   INT NOT NULL,
   CONSTRAINT fk_slots_doctor FOREIGN KEY (doctor_id) REFERENCES doctors(id)
 );

 CREATE TABLE appointments (
   id         INT AUTO_INCREMENT PRIMARY KEY,
   version    INT NOT NULL DEFAULT 0,
   day        DATE NOT NULL,
   slot_id    INT NOT NULL,
   client_id  INT NOT NULL,
   CONSTRAINT fk_appointments_slot   FOREIGN KEY (slot_id)   REFERENCES slots(id),
   CONSTRAINT fk_appointments_client FOREIGN KEY (client_id) REFERENCES clients(id),
   CONSTRAINT unq1_appointments UNIQUE (day, slot_id)
 );

 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,
   name     VARCHAR(40) NOT NULL,
   role     VARCHAR(10) NOT NULL
 );

 INSERT INTO doctors (title, last_name, first_name) VALUES
   ('Mme', 'PELISSIER', 'Marie'), ('Mr', 'TATOU', 'Paul'), ('Melle','DUPONT', 'Sophie');

 INSERT INTO clients (title, last_name, first_name) VALUES
   ('Mr', 'MARTIN', 'Jean'), ('Mme', 'DURAND', 'Alice'),
   ('Melle','BISTROU', 'Brigitte'), ('Mr', 'GERMAN', 'Luc');

 INSERT INTO slots (doctor_id, start_hour, start_minute, end_hour, end_minute) VALUES
   (1, 8, 0, 8, 20), (1, 8, 20, 8, 40), /* ... */ (1, 11, 40, 12, 0);

 INSERT INTO slots (doctor_id, start_hour, start_minute, end_hour, end_minute) VALUES
   (2, 14, 0, 14, 20), (2, 14, 20, 14, 40), /* ... */ (2, 17, 40, 18, 0);

 INSERT INTO appointments (day, slot_id, client_id) VALUES (CURDATE(), 2, 3);

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

Let’s comment on this code:

  • Lines 19–23: [DROP TABLE IF EXISTS …] — the script starts with an empty database on each execution, in the reverse order of dependencies (starting with `appointments`, which references `[slots]` and `clients`, all the way down to `users`, which is not referenced by anyone): [MySQL] would refuse to delete a table still referenced by a foreign key;
  • line 47: [doctor_id INT NOT NULL], then line 52: [CONSTRAINT fk_slots_doctor FOREIGN KEY …] — a time slot always belongs to a doctor;
  • line 63: [CONSTRAINT unq1_appointments UNIQUE (day, slot_id)] — the application’s central business rule: the same time slot on the same day can be booked only once. It is this constraint, and this constraint alone, that prevents a duplicate appointment—neither the controller nor the business layer explicitly rechecks it;
  • lines 66–73: [CREATE TABLE users (…)] — the table added by this step. `login` is UNIQUE (two users cannot share the same login ID); `password` stores a password hashed using bcrypt (never in plaintext); role is either 'ADMIN' or 'USER'—a simple text column, whereas the original document used three tables (users, roles, users_roles) to allow multiple roles per user. Since this application requires only two mutually exclusive roles, the original many-to-many relationship would have been more than necessary;
  • lines 90–92: [INSERT INTO users …] — two demo accounts, admin/admin (role ADMIN) and user/user (role USER), with their passwords already hashed using bcrypt (10 rounds of salting)—never in plaintext, including in this demo script.

3.3.2. Running the script with HeidiSQL

To create the [MySQL] database required by the [NestJS] server, we proceed as follows:

 
  • In [1], all services must be started;
  • in [2], the [Database] option allows you to manage the database;

The [Database] option launches a utility called HeidiSQL:

  • In [1], you can select various DBMS options. The default value provided is MySQL. This is acceptable;
  • in [2], you log in to HeidiSQL;
  • In [1-2], run the SQL file, then select the [dbrdvmedecins.sql] file, which you’ll find in the [rdvmedecins-nestjs-server/database] folder:

Image

Running the [3] file will create the [dbrdvmedecins] database used by the [NestJS] server. To view it, run F5 within HeidiSQL to refresh the display:

Image

The database [dbrdvmedecins] has been created. You can view its contents:

Image

  • In [1-3], the table [clients];
    • [id] is the primary key of the table;
    • [version] is the row’s version number. This number increments each time the row is modified;
    • [title, last_name, first_name] identifies the person;

Image

  • in [1-3], the table [doctors];
    • [id] is the primary key of the table;
    • [version] is the version number of the row;
    • [title, last_name, first_name] identifies the person;

Image

  • In [1-3], the table of doctors’ appointment time slots:
    • [id]: primary key;
    • [version]: row version number;
    • [doctor_id]: foreign key referencing the doctor’s primary key;
    • [start_hour, start_minute]: hour and minutes of the appointment start time. Thus, [8,20] means 8:20 a.m.
    • [end_hour, end_minute]: hour and minutes of the end of the consultation.

Image

  • In [1-3], the [appointments] table of scheduled appointments;
    • [id]: primary key;
    • [version]: version number;
    • [day]: appointment date;
    • [slot_id]: foreign key referencing the primary key of the appointment time slot. Since a time slot is linked to a doctor, we know which doctor is handling this appointment;
    • [client_id]: foreign key to the number (primary key) of the patient attending the appointment;

Image

  • in [1-3], the table [users], which lists the users authorized to use the application [NestJS];
    • [id, version]: primary key, version number;
    • [login, password]: user credentials;
    • [name]: user name;
    • [role]: the user’s role. [ADMIN] has full access rights. [USER] has read-only access. This user can view a doctor’s schedule but cannot schedule or cancel an appointment;

3.4. Starting the [NestJS] server

Start all Laragon services ([Start All]), if you haven’t already. Then start the NestJS server in a VSCode terminal for the NestJS project:

npm run start:dev

The console displays, in order: the startup of the [NestJS] module, the list of routes exposed by the controllers, and then confirmation of startup:

[Nest] RoutesResolver RdvMedecinsController {/}:
[Nest] RouterExplorer Mapped {/getAllDoctors, GET} route
[Nest] RouterExplorer Mapped {/getAllClients, GET} route
[Nest] RouterExplorer Mapped {/getAllSlots/:doctorId, GET} route
[Nest] RouterExplorer Mapped {/getDoctorAppointmentsOfDay/:doctorId/:day, GET} route
[Nest] RouterExplorer Mapped {/getDoctorScheduleOfDay/:doctorId/:day, GET} route
[Nest] RouterExplorer Mapped {/getDoctorById/:id, GET} route
[Nest] RouterExplorer Mapped {/getClientById/:id, GET} route
[Nest] RouterExplorer Mapped {/getSlotById/:id, GET} route
[Nest] RouterExplorer Mapped {/getAppointmentById/:id, GET} route
[Nest] RouterExplorer Mapped {/addAppointment, POST} route
[Nest] RouterExplorer Mapped {/deleteAppointment, POST} route
[Nest] RoutesResolver AuthController {/}:
[Nest] RouterExplorer Mapped {/login, POST} route
[Nest] NestApplication Nest application successfully started
[RdvMedecins] NestJS server started on http://localhost:8080

This console output confirms that the server is indeed listening on port 8080—exactly the same port as the Spring Boot server in the original document, which was deliberately chosen for this reason.

These routes constitute the JSON API that the client [Flutter] will consume in Chapter 4. [POST /login] is the only public route: it returns a JWT token that all other routes require in the header [Authorization: Bearer <token>]. The read-only routes ([GET …]) are accessible to the roles ADMIN and USER; the two routes that modify data, [POST /addAppointment] and [POST /deleteAppointment], are reserved for the ADMIN role. Business responses are wrapped in a [{ status, data }] object, where [status] is set to 0 upon success.

3.5. Test the server with a browser

Since authentication was added, all routes in [RdvMedecinsController] require a valid JWT token; therefore, a browser alone can no longer be used to test them directly. For example:

http://localhost:8080/getAllDoctors

now returns a 401 Unauthorized error without a token:

Image

This is the expected behavior: this response confirms that the server is properly protecting its routes. Postman (next section) is required to obtain a token and then use it.

3.6. Testing the [NestJS] server with Postman

The Postman tool is available at URL [https://www.postman.com/downloads/]. You may need to create an account.

Start all Laragon services, then open the Postman desktop application:

  • In [1-2], create a request HTTP to query the doctors’ appointment management server;

3.6.1. Log in and obtain a token

  1. Create a new Postman request with the method POST and the URL http://localhost:8080/login;
  2. In the [Body] tab, select "raw," then the JSON format, and enter: {"login": "admin", "password": "admin"} (or "user/user" to test the USER role);
  3. Submit the request.

Image

  • in [1-2], the request POST to the server [NestJS] running on port 8080;
  • to [3-5], the body of the request. This transmits to the server the string JSON containing the credentials of the user attempting to log in, in this case admin/admin;
  • in [6], the server’s response;
  • in [7], the authentication token that subsequent requests must send to the server to indicate “I have already authenticated”;

Copy the value from accessToken: this is the token you must now include in every subsequent request. In the example above, the token to copy is “eyJ...zoM” (without the quotes).

3.6.2. Calling a protected route with the token

For GET /getAllDoctors (or any other route of RdvMedecinsController), in Postman, on the Authorization tab, select the Bearer Token type and paste the token copied above (Postman will then automatically add the Authorization: Bearer <token> header).

Image

  • in [1-2], the request sent to the server. It requests the list of doctors in the database;
  • In [3-5], the request must include the authentication token. In [4], select [Bearer Token], and in [5], paste the authentication token you copied earlier;
  • in [7], the server’s response;

3.6.3. Test the POST routes (/addAppointment, /deleteAppointment)

Follow the same procedure as above (method POST, body JSON, Bearer Token), exactly as the original document used the Chrome extension “Advanced Rest [Client]” for the same routes.

Note: You can find the following Postman requests in the [rdvmedecins-nestJS.postman_collection.json] collection:

Image

In Postman, press Ctrl-0 to load the collection:

Image

  • Click on [1] to load the collection and select the collection file. Once the collection is loaded, it displays its requests:

Image

To test /addAppointment with the token from an account ADMIN:

  1. Postman request, method POST, URL http://localhost:8080/addAppointment;
  2. Authorization tab: Bearer Token, admin token;
  3. [Body] tab, raw, JSON: {"day": "2026-09-13", "clientId": 1, "slotId": 5};
  4. Send the request.

Image

  • in [3-5], the request body;

Image

You can verify with HeidiSQL that an appointment has been added to the database:

Image

To test /deleteAppointment, follow the same procedure with the body {"appointmentId": 2} (the ID of the appointment to be canceled (8) above).

Image

  • in [4], where the number above is the appointment to be deleted;

Image

  • to [5-7], the authentication token;
  • to [9], the server’s response. [status=0] indicates that the operation was successful. You can verify this with HeidiSQL:

Image

  • in [3], appointment #2 no longer exists;

Try this: repeat the same request /addAppointment using the token for the USER account (user/user) instead of the admin token: the response becomes 403 Forbidden, with the message “role [USER] does not allow this action (required role: ADMIN)”—a concrete demonstration of the role-based access control implemented by the server.

3.7. Launch the Flutter client for the first time

In the [rdvmedecins_flutter_client] folder (the [NestJS] server must be started beforehand):

flutter devices          # lists the targets available on this machine
flutter run -d chrome    # web - the simplest target for a first try

Image

The login screen appears first, just as it did for the previous clients in this course (see Chapter 4): two demo accounts are provided with the server database, [admin]/[admin] (role [ADMIN], full access) and [user]/[user] (role [USER], read-only). Two buttons labeled “FR” and “EN” in the upper-right corner allow you to switch the interface to French, even before logging in. Chapter 5 details the equivalent commands for desktop and mobile, as well as the only setting specific to these platforms (the server address; see later in this chapter).