← Voir tous les articlesGrégory TAUDINGrégory TAUDIN - 2 juin 2022

NestJS : Installation et création d'un projet monorepo en architecture CQRS

NestJS logo

NestJS calque ses concepts internes sur ceux d'Angular dont il reprend l'architecture MVC et ses principaux patterns, TypeScript pour programmer et utilise Express ou Fastify pour la partie service HTTP. Il permet donc comme son homologue front de récupérer une stack technique très intégrée, éprouvée contenant un ensemble de fonctionnalités prêt à être utilisé et se suffit à lui-même.

Dans cet article, nous allons concevoir une API qui nous permettra de comprendre les concepts de base de NestJS ainsi que l'implémentation de certaines fonctionnalités très cool !

Car un des avantages immédiats de NestJS : Il permet de construire une API REST ou des micro-services très rapidement.

Installation

Avant toute chose, nous avons besoin de quelques prérequis :

  • NodeJS 16,
  • NPM,
  • Visual Studio Code (VS) ou autre (à vous de choisir !).

Créons un dossier qui contiendra notre nouvelle stack NestJS. Depuis VS code, ouvrons un terminal dans le dossier et lançons la commande suivante :

> npm i -g @nest/cli

Cette commande « npm » installe la CLI de NestJS en global. Ensuite lançons la commande :

> nest

La CLI de NestJS permet de créer tous les éléments ci-dessous :

Aide la CLI NestJS.

Pour créer un nouveau projet directement avec le paramètre « new », tapons la commande suivante :

> nest new my-technical-stack

La CLI va créer pour nous un nouveau projet dans le dossier my-technical-stack :

  • src : contient notre future application.
  • test : contient les tests End to End.

Les fichiers de configuration nécessaires à la construction de l'application:

  • .prettierrc : fichier de configuration du formateur de code Prettier.
  • nest-cli.json : fichier de configuration de la CLI NestJS.
  • package.json : fichier de configuration de l'application NodeJS.
  • tsconfig.json : fichier de configuration TypeScript.

Notre première application NestJS est fonctionnelle ! Par défaut, la CLI de Nest génère une API de type REST. Allons dans le dossier « my-technical-stack », ouvrons un terminal et lançons la commande :

> npm start

Le serveur NestJS se lance à l'adreee http://localhost:3000. Le rendu est immédiat avec un beau « Hello world » en pleine page ! Etudions de plus près les fichiers applicatifs, sélectionnons le dossier « src ».

5 fichiers sont présents :

  • app.controller.spec.ts : fichier des tests unitaires.
  • app.controller.ts : le contrôleur de notre application.
  • app.module.ts : le module de notre application.
  • app.service.ts : le service de notre application.
  • main.ts : le fichier de chargement de notre application.

Dans le fichier "main.ts" est défini une fonction asynchrone « bootstrap » qui appelle l'objet « NestFactory » afin de créer l'application définie dans le fichier « app.module.ts ».

Dans le fichier « app.module.ts », (pour les développeurs Angular, remarquez les similarités dans les décorateurs de l'application avec ceux d'un module Angular), le décorateur « @Module » permet de définir les « controllers » et « providers » qui seront utilisés.

Le service « app.service.ts » ou provider expose le décorateur « @Injectable » qui permet à NestJS de répertorier ce service pour les injections de dépendance. Il définit une fonction « getHello » qui renvoie une simple chaîne de caractères « Hello World ».

Le contrôleur « app.controller.ts » quant à lui va permettre d'exposer une méthode de type GET et appelle le service AppService au travers de l'injection de dépendance du service (grâce au constructeur de la classe « AppControlleur »).

Super efficace non ?

Monorepo

Dans cet exemple, nous allons créer un projet de type monorepo :

  • Un applicatif M-O-N-O-R-E-P-O, on te dit !
  • Mononoke ? QUOI ! Mais c'est pas un film d'animation ça ?
  • Nope ! Pas de panique, juste du jargon de plus dans notre catalogue déjà très fourni… d'après Wikipedia : « Un mono-repository est une stratégie de développement où le code de plusieurs projets est stocké dans un seul et même endroit. »
  • Ha ! oui ! Donc tout est dans un dossier quoi !
  • C'est ça… Et pendant que j'y suis va voir la documentation : https://docs.nestjs.com/cli/monorepo#monorepo-mode

Repartons de notre application. Dans le dossier « my-technical-stack », ouvrons un terminal et lançons la commande :

> nest generate app reader

La CLI va transformer la structure des fichiers de notre application. Un nouveau dossier « apps » est créé et nous y retrouvons nos deux applications :

  • my-technical-stack : notre application de base (vous pouvez la supprimer mais attention aux dépendances dans tous les fichiers de configuration).
  • reader : notre nouvelle application.

Recommençons et créons une nouvelle application :

> nest generate app writer

Une troisième application est ajoutée au dossier apps. Enfin, créons une librairie pour partager notre code :

> nest generate lib shared

(Répondre par l'affirmative au message « ? What prefix would you like to use for the library (default: @app). »).

Notre squelette applicatif est prêt !

  • reader : une application qui permettra de requêter notre future base de données avec uniquement des requêtes de type « lecture ».
  • writer : une application qui ne s'occupera que des requêtes de type « écriture ».
  • lib : notre librairie de code partagée entre toutes nos applications.

Mais au fait, pourquoi un service par type de requête ?

CQRS

CQRS signifie Command and Query Responsibility Segregation, un modèle qui sépare les opérations de lecture et de mise à jour pour un repository de données. L'implémentation de CQRS dans votre application peut optimiser ses performances, son évolutivité et sa sécurité. La flexibilité créée par la migration vers CQRS permet à un système de mieux évoluer dans le temps et empêche les commandes de mise à jour de provoquer des conflits de fusion au niveau du domaine.

Dans beaucoup de domaines, les services de type lecture sont souvent très sollicités par rapport aux services en écriture. Il est donc capital que la scalabilité des services de type lecture soit différente des services de type écriture, d'où le split en plusieurs applications. Un autre point important est la structure de la base de données derrière ces 2 services, un service de lecture peut utiliser une base de données non structurée (utilisant du NOSQL par exemple) avec une structure plate pour augmenter la vitesse d'IO tandis qu'un service d'écriture doit écrire des données dans une base de données structurée, ce qui est, généralement, une opération plus longue.

Plusieurs adaptations sont à réaliser dans le code. Dans chaque « main.ts » des services, il faut mettre un port différent (3000, 4000, 5000). Ensuite, il faut, dans chaque module des différents projets, référencer la librairie « shared ».

Allons dans « apps/reader/reader.module.ts » et « apps/writer/ writer.module.ts » :

Ajoutons dans le décorateur @Module, dans le tableau « imports », la référence vers « SharedModule ». Une dernière étape consiste à modifier le fichier package.json pour ajouter nos différents builds.

Dans le fichier, rajoutons les lignes suivantes sous la ligne « scripts/build » :

"build:reader": "nest build reader",
"build:writer": "nest build writer"

Modifions ensuite la commande « build » de base pour tout faire en même temps :

"build": "nest build && nest build reader && nest build writer"

Testons la commande "build" :

> npm run build

Allons dans le dossier « dist » à la racine du projet. A l'intérieur : 3 dossiers contenant nos trois applicatifs => Ils sont tous autonomes, nous pourrons ainsi les déployer facilement avec par exemple Docker.

Mission complete!

Notre architecture est prête pour recevoir l'implémentation du code ! Nous pouvons penser à la prochaine étape : L'approche Code First avec TypeOrm.