Un premier projet avec Node.js
Introduction
Le but de ce chapitre est de mettre en pratique les différentes notions apprises dans les précédents chapitres de l’ouvrage. L’objectif est de développer une API à l’aide de TypeScript et Node.js. Elle sera construite en plusieurs étapes afin de pouvoir expliquer pas à pas les concepts mis en pratique.
Une API ou application programming interface (interface de programmation d’application) est caractérisée par un ensemble de classes/méthodes/fonctions offrant des services. L’objectif principal est de réduire la complexité d’implémentation de ces services en fournissant une façade utilisable par des applications tierces. Ces services sont mis en œuvre via des points de terminaison (endpoints).
Pour développer cette API, il est nécessaire de s’appuyer sur une architecture REST (REpresentational State Transfer). Ce style d’architecture se base sur les verbes et codes HTTP afin de simplifier la compréhension et l’utilisation d’une API.
Dans le cadre de cette mise en pratique, vous allez créer un annuaire d’entreprise permettant de gérer les employés d’une société.
Pour manipuler une API Rest, il est nécessaire d’utiliser un client. Inutile d’installer un logiciel pour cela, car un explorateur d’API sera...
Mise en place du projet
Dans cette section, vous allez mettre en place la structure du projet.
Créez un répertoire de votre choix qui contiendra l’intégralité de l’application.
Ouvrez ce répertoire avec Visual Studio Code.
Créez ensuite un sous-répertoire src.
Dans le dossier src, ajoutez le fichier index.ts.
Depuis Visual Studio Code, ouvrez la palette de commandes (à l’aide du raccourci-clavier [Ctrl][Shift] P sous Linux/Windows et [Cmd][Shift] P sous macOS).
Ouvrez le terminal de Visual Studio Code en exécutant la commande Toggle Terminal:
Les différentes commandes énoncées dans les prochaines sections peuvent être saisies dans n’importe quel terminal. Toutefois, l’utilisation du terminal intégré de Visual Studio Code est conseillée, car plus confortable.
1. Création du fichier package.json
Pour mettre en place une application Node.js, il est nécessaire d’utiliser la commande NPM init pour créer un fichier package.json à la racine du projet. Ce fichier contient un certain nombre d’informations propres à l’application (nom, description, auteur, version, licence, dépendances…).
Dans le terminal intégré de Visual Studio Code, entrez la commande suivante :
npm init -y
À partir de cette étape, dès qu’une commande doit être exécutée dans le terminal intégré de Visual Studio Code, celui-ci devra être positionné à la racine du projet.
Ouvrez le fichier package.json et modifiez la propriété name avec la valeur employee-directory :
"name": "employee-directory"
Ajoutez ensuite la propriété suivante :
"private": true
Ce champ est indispensable pour empêcher toute publication de l’application. Il limite donc le risque de fausse manipulation.
Avant de continuer, vérifiez que le fichier package.json contient les informations suivantes :
{
"name": "employee-directory",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts":...
Création du Framework MVC
L’objectif de cette section est d’utiliser certains concepts vus dans les chapitres précédents de cet ouvrage (comme les décorateurs, les types littéraux…). Cependant, pour un projet d’entreprise, il n’est pas conseillé de créer son propre Framework MVC mais plutôt d’utiliser une librairie tierce (par exemple Nest.js, Loopback, Sails.js, FoalTS...).
Modèle-Vue-Contrôleur (MVC) est un patron de conception très populaire pour développer des applications web. Dans une application MVC, les responsabilités sont réparties entre les éléments suivants :
-
Le modèle : il contient uniquement les données de l’application et ne possède aucune logique.
-
La vue : elle présente les données contenues dans la partie modèle.
-
Le contrôleur : il contient la logique de manipulation des données et il réagit aux actions de l’utilisateur afin de mettre à jour le modèle.
Ces parties interagissent selon le schéma suivant :
Dans cette section, vous allez créer la partie contrôleur. La partie modèle sera mise en place par la suite.
1. Registre des routes
Le registre des routes permet de créer l’intégralité des contrôleurs et leurs actions. Cela permet de monter l’ensemble des routes de l’application avec le routeur fourni par Fastify.
Dans le répertoire src, créez un répertoire core.
Dans le répertoire core, créez un répertoire mvc.
Dans le répertoire mvc, créez le fichier types.ts.
Dans ce fichier, ajoutez l’alias de type HttpVerb contenant le type littéral "get" et exportez-le :
export type HttpVerb = "get";
D’autres verbes HTTP seront définis par la suite lorsqu’ils seront nécessaires pour le bon fonctionnement de l’application.
Ajoutez ensuite une interface définissant les informations relatives à une route. Elle permet de récupérer le nom du contrôleur, celui de l’action à exécuter, ainsi que le verbe http et le path permettant d’y accéder dans le contexte d’une application web. Ces informations sont...
Validation et OpenApi
Dans cette partie, il s’agit de mettre en place la création des employés dans l’annuaire d’entreprise. Pour ce faire, il est nécessaire de créer une action à exécuter lors d’un appel HTTP de type POST. Les données fournies dans le corps de la requête sont validées à l’aide d’un schéma JSON. Fastify implémente nativement la validation de document JSON via la spécification JSON Schema (https://json-schema.org/).
L’un des intérêts de se baser sur la validation de schéma est qu’elle permet aussi, via d’autres bibliothèques de l’écosystème Fastify, de mettre en place une description et un explorateur d’API de type OpenApi (https://www.openapis.org/).
1. Typage des schémas
La validation de schéma est une fonctionnalité qui nécessite plusieurs parties. Pour commencer, nous allons définir les éléments permettant de créer le schéma JSON de validation d’un modèle.
Dans le répertoire core, créez un répertoire schema.
Dans le répertoire schema, créez un fichier types.ts.
Ajoutez ensuite l’alias de type SchemaBaseProperty. Ce type définit la structure de base d’une propriété d’un schéma. Ajoutez-y une propriété description de type string. Elle sera utile pour afficher les informations de la propriété dans l’explorateur d’API.
type SchemaBaseProperty = {
description: string;
};
Ensuite, définissez un alias de type SchemaIntegerProperty permettant de valider une valeur de type Integer. Ajoutez une propriété type ayant pour type "integer", et une propriété optionnelle minimum pour valider que l’entier est bien supérieur ou égal à une valeur donnée.
type SchemaIntegerProperty = {
type: "integer";
minimum?: number;
};
À présent, définissez un alias de type SchemaStringProperty permettant de valider une valeur de type String. Ajoutez une propriété type ayant pour type "string", et une propriété...
Accès et persistance des données
Accéder et persister les données directement depuis un contrôleur est considéré comme une mauvaise pratique, car cela multiplie les responsabilités du contrôleur et ne permet pas la réutilisabilité de ce code.
Il s’agit donc d’implémenter une vraie couche d’accès aux données en utilisant le patron de conception Repository. Il permet de découpler la persistance des données du reste de l’application en offrant une couche d’abstraction (correspondant à un dépôt de données), et il est utilisable, quel que soit le mode de persistance des données (base de données SQL, fichier plat, données en mémoire…). Ce design pattern accroît la testabilité et la maintenabilité du programme. Grâce à la généricité, il centralise la manière de manipuler les différents objets métier de l’application, évitant ainsi la duplication de code.
Ce patron de conception ne doit pas être confondu avec les outils appelés ORM (Object Relational Mapping ou Mapping Objet relation). Les ORM facilitent l’utilisation d’une base de données SQL dans une application utilisant la programmation orientée objet. Ils peuvent générer automatiquement les requêtes SQL. Contrairement aux ORM, le pattern Repository n’est pas lié à un mode de persistance.
1. Typage des entités métier
Dans le répertoire core, créez un répertoire data.
Dans le répertoire data, créez le fichier types.ts.
Dans le fichier types.ts, ajoutez une interface IEntity. Cette interface définit une propriété id qui sera commune à l’ensemble des entités du projet :
export interface IEntity {
id: string;
}
Dans le répertoire src, créez un répertoire entities.
Dans le répertoire entities, créez un fichier employee.ts.
Dans le fichier employee.ts, importez l’interface IEntity.
import type { IEntity } from "../core/data/types";
Ajoutez la classe Employee et exportez-la. Implémentez ensuite l’interface...
Inversion des dépendances
Dans la section précédente, la classe Repository crée elle-même une instance de la classe MemoryStorage, afin de l’utiliser comme une abstraction dans le reste de la classe (définie par l’interface IStorage). Cette implémentation ne respecte pas le principe SOLID d’inversion des dépendances (cf. chapitre La programmation orientée objet).
Dans cette section, il s’agit de mettre en place l’injection de dépendances afin de respecter ce principe et par conséquent découpler le Repository du stockage des données. De plus, vous définirez une implémentation de l’interface IStorage permettant de persister les données dans un fichier. Cette implémentation pourra ensuite être changée au niveau d’un conteneur de dépendances afin d’être injectée dans le repository.
Comme pour les frameworks MVC ou encore les ORM, il existe en TypeScript des bibliothèques permettant de gérer les dépendances (par exemple InversifyJS). L’objectif de cette section est de mettre en pratique certains des concepts vus dans les chapitres précédents de l’ouvrage, mais pas de remplacer de telles bibliothèques.
1. Le conteneur de dépendances
Dans le répertoire core, créez un répertoire nommé ioc.
L’acronyme IoC correspond à Inversion of Control (inversion de contrôle en français). C’est un pattern qui permet de mettre en place l’inversion des dépendances en passant par un tiers dédié à la gestion des dépendances (plus communément appelé conteneur de dépendances).
Dans le répertoire ioc, créez un fichier nommé dependencyContainer.ts.
Dans le fichier dependencyContainer.ts, ajoutez une classe DependencyContainer et exportez-la.
export class DependencyContainer {
}
Définissez une propriété privée statique #instance dans la classe DependencyContainer. Cette propriété permettra de stocker l’instance unique de la classe :
static #instance: DependencyContainer;
Ajoutez ensuite un constructeur privé.
private constructor() {
}
Pour finir...
Allez plus loin
Félicitations, vous êtes parvenu jusqu’à la fin de cet exercice et vous avez pu mettre en œuvre une grande partie des fonctionnalités décrites dans cet ouvrage !
Afin de continuer le projet et surtout la mise en pratique du langage TypeScript, voici une liste d’idées (non exhaustive) d’ajouts que vous pouvez expérimenter :
-
Abstraire et injecter la classe Repository.
-
Ajoutez une description Swagger à l’API.
-
Gérer les schémas de retour des actions afin d’avoir la description des réponses HTTP.
-
Persister les données dans une base de données.
-
Construire une application Frontend utilisant l’API Rest (par exemple avec React, Angular ou Vue).
-
Ajouter le verbe HTTP PUT et gérer la mise à jour des données.
-
Gérer la copie des données contenues dans les paramètres de requête et de route dans les modèles.
-
…