Construire Un Serveur Backend Robuste Avec NestJS 🚧

Damien Chazoule
11 min readOct 25, 2021

--

Il est temps d’en parler
 Pas Next, ni Nuxt, mais Nest !

P r o l o g u e

RĂ©cemment, je me suis challengĂ© pour mettre en Ɠuvre une solution backend robuste en JavaScript. Beaucoup de dĂ©veloppeurs ont pour prĂ©jugĂ© de croire que le JavaScript n’est pas assez sĂ©curisĂ©, performant ou tout simplement organisĂ© pour gĂ©rer des donnĂ©es via des APIs. Je vais vous persuader du contraire


Pour ce nouveau projet, j’ai choisi des technologies qui ont le vent en poupe, notamment PostgreSQL mais surtout NestJS. Ainsi, j’ai abandonnĂ© ma base de donnĂ©es de toujours qu’est MongoDB, au profit d’une base de donnĂ©es plus traditionnelle.

NB : J’étais un peu rouillĂ© au dĂ©but, mais le retour Ă  la base de donnĂ©es relationnelle Ă©tait une vraie bonne idĂ©e ; ne serait-ce que pour rĂ©cupĂ©rer des donnĂ©es dans plusieurs tables en un appel, au contraire du NoSQL qui nĂ©cessite autant d’appels qu’il y a de documents Ă  interroger
 Finalement, le SQL c’est comme le vĂ©lo, cela ne s’oublie pas 😉

NestJS est un framework permettant de crĂ©er des applications serveurs (propulsĂ©es par NodeJS) Ă  haute scalabilitĂ©. Ce dernier s’appuie notamment sur TypeScript et combine trois paradigmes de dĂ©veloppement :

  • La Programmation OrientĂ©e Objet #POO
  • La Programmation Fonctionnelle #PF
  • La Programmation RĂ©active Fonctionnelle #PRF

NestJS part du constat que les frameworks Web (notamment ceux orientĂ©s composants), ne sont pas assez structurĂ©s
 Sauf un : Angular ! Il s’inspire donc de celui-ci afin de fournir une librairie complĂšte pour les dĂ©veloppements backend.

NB : En effet, Angular dispose d’une organisation similaire, mais contrairement Ă  NestJS, la structure de celui-ci est (Ă  mon avis) plus contraignante qu’autre chose
 Je prĂ©fĂšre ses homologues React et Vue (et Svelte) qui offrent plus de flexibilitĂ© dans la conception d’une application frontend. Cependant, je suis persuadĂ© que cette “lourde” architecture peut ĂȘtre une aide significative Ă  ceux qui dĂ©butent en dĂ©veloppement frontend.

Enfin, NestJS encapsule (au choix) le moteur ExpressJS ou celui de Fastify, pour livrer un serveur HTTP robuste et efficace. On peut donc parler d’un framework de frameworks (meta-framework !? đŸ€”) Pour ce projet, j’ai choisi d’utiliser Fastify qui semble plus que prometteur


I n i t i a l i s a t i o n

Il est temps de coder ! đŸ§‘â€đŸ’» Commençons par installer la dĂ©pendance principale de NestJS, puis initialisons un nouveau projet : hello-community

npm i -g @nestjs/cli
nest new hello-community

Alors, vous la voyez la ressemblance avec Angular ? 🙃 En utilisant l’interface en ligne de commande de NestJS, on se retrouve avec une architecture “modules — controllers — services” :

  • Les controllers se chargent d’exposer les routes de l’application
  • Les services s’occupent de la communication avec la / les source(s) de donnĂ©es
  • Les modules mettent en relation les services avec les controllers

NB : En plus de l’initialisation du projet, l’outil CLI de NestJS va nous permettre de “scaffolder” individuellement ces mĂȘmes fichiers (nest generate <module|controller|service>). PlutĂŽt pratique !

Maintenant, intĂ©ressons-nous davantage au fichier main.ts, point d'entrĂ©e de l'application. Par dĂ©faut, NestJS va monter un serveur HTTP en se basant sur ExpressJS. Or, dans mon cas, je vais avoir besoin de Fastify, qui (pour rappel) est censĂ© ĂȘtre plus rapide et lĂ©ger que son homologue.

Pour cela, il est nĂ©cessaire d’ajouter la dĂ©pendance @nestjs/platform-fastify au projet, puis d'adapter le code prĂ©sent dans le fichier main.ts.

main.ts

Et voilĂ  ! Il vous suffit de vous rendre Ă  l’adresse http://localhost:3000 pour admirer votre premier Hello World! (ou bien Hello Community! si vous avez Ă©tĂ© curieux) 👏

NB : En prĂ©cisant la chaĂźne de caractĂšres 0.0.0.0 en tant que deuxiĂšme paramĂštre de la fonction listen(), vous pourrez accĂ©der Ă  vos endpoints dans un mĂȘme rĂ©seau local.

Difficile de voir une vraie diffĂ©rence entre ExpressJS et Fastify avec un simple appel. Quoique
 J’ai remarquĂ© une lĂ©gĂšre diffĂ©rence au moment du chargement. D’aprĂšs Google Chrome (confirmĂ© par Mozilla Firefox), ce premier Hello World! charge 239 octets avec ExpressJS, contre 176 octets via Fastify. Force est de constater que ce framework est (au moins) plus lĂ©ger que son prĂ©dĂ©cesseur.

C R U D

Rendons les choses plus intĂ©ressantes / plus concrĂštes en mettant en Ɠuvre notre premiĂšre API REST(ful) avec NestJS. Pour cela, on peut utiliser l’outil CLI pour crĂ©er des fichiers indĂ©pendamment (module / controller / service), ou bien gĂ©nĂ©rer tout l’ensemble directement (c’est-Ă -dire une nouvelle ressource CRUD).

La commande nest g resource fait cela pour nous. En renseignant seulement le nom de la ressource (users) et en prĂ©cisant qu'il s'agit d'une API REST, on obtient rapidement un controller et un service prĂȘts Ă  l'emploi.

L’un des avantages de ce mode de fonctionnement, c’est que NestJS gĂ©nĂšre Ă©galement certains patterns de dĂ©veloppement, notamment le DTO (Data Transfer Object) bien connu des dĂ©veloppeurs. #POO

users.service.ts

Pour cette premiĂšre ressource, je vous propose de conserver le controller en l’état (users.controller.ts), mais d'adapter le code du service (users.service.ts), afin de simuler une source de donnĂ©es.

NB : Pour des soucis pratiques (rĂ©duction de la taille de cet article), j’ai choisi de faire apparaitre le type de mes donnĂ©es. Dans un cas rĂ©el, je vous conseille de ranger cela aux bons endroits, Ă  savoir dans des dossiers interfaces et enums (ou tout simplement models).

Assurez-vous d’avoir enregistrĂ© votre module users.module.ts au niveau du module principal (app.module.ts), puis lancer l'application (npm run start). Vous devriez pouvoir commencer Ă  jouer avec votre API via le protocole REST (et Postman bien sĂ»r 😎).

S Ă© c u r i t Ă©

Les choses sĂ©rieuses dĂ©butent ici
 Si on reviens un peu en arriĂšre, on remarque que nos endpoints sont tous accessibles avec le mĂȘme niveau de sĂ©curitĂ© (c’est-Ă -dire aucun). De plus, le mot de passe de l’utilisateur est sauvegardĂ© en clair en base de donnĂ©es
 Il convient donc de sĂ©curiser tout cela !

PremiĂšre Ă©tape : l’offuscation du mot de passe. NestJS supporte le chiffrement de donnĂ©es (grĂące au module crypto de NodeJS), mais aussi le hachage de donnĂ©es. Ici, j’ai choisi d’utiliser la librairie bcrypt pour “hasher” mes chaines de caractĂšres, afin de garantir une sĂ©curitĂ© unidirectionnelle (et optimale) ! 👌

Installons les dépendances (npm i --save bcrypt && npm i --save-dev @types/bcrypt) et modifions le service de création d'utilisateur pour sécuriser le mot de passe.

users.service.ts

DeuxiĂšme Ă©tape : sĂ©curiser les appels en utilisant un protocole d’authentification. Cette seconde tĂąche est plus complexe puisqu’elle nĂ©cessite de mettre en place une ou plusieurs stratĂ©gies d’authentification. Fort heureusement, NestJS dispose dĂ©jĂ  d’un concept de “guard” permettant “de valider / d’invalider” les routes de l’application. C’est partie !

npm install --save @nestjs/passport passport passport-local passport-jwt
npm install --save-dev @types/passport-local @types/passport-jwt

Le meilleur moyen d’établir ce genre de fonctionnement (avec NodeJS) est d’utiliser Passport. CĂŽtĂ© conception, je vais devoir crĂ©er une nouvelle route d’authentification qui me fournira un jeton permettant de valider mes endpoints /users/:id (GET, PATCH et DELETE). VoilĂ  pourquoi, je rĂ©cupĂšre (ci-dessus) deux stratĂ©gies :

  • La stratĂ©gie LOCAL, permet une authentification simple (basĂ© sur le couple username / password)
  • La stratĂ©gie JWT, s’appuie sur la validitĂ© d’un jeton dans le temps

À nouveau, il va falloir utiliser l’interface en ligne de commande pour instancier un module, un controller ainsi qu’un service (nest g <module|controller|service> auth). De plus, nous allons devoir crĂ©er deux nouveaux fichiers correspondants respectivement aux stratĂ©gies de Passport.

local.strategy.ts

NB : Vous noterez ici que je surcharge le comportement par défaut de la stratégie LOCAL, pour utiliser le couple email / password.

jwt.strategy.ts

NB : Dans un cas rĂ©el, je vous dĂ©conseille d’exposer le secret JWT ! Il est prĂ©fĂ©rable de le rĂ©cupĂ©rer autrement, via le fichier .env (le module @nestjs/config peut alors ĂȘtre utile) ou un autre procĂ©dĂ©...

auth.service.ts
auth.controller.ts
auth.module.ts

Aprùs tout cela, quelques explications s’imposent. Commençons avec le controller


Lors de l’appel du endpoint /login, l'utilisateur va "POSTer" ses identifiants au format email / password.

NestJS va alors protĂ©ger la route par la “guard” LOCAL qui s’occupera de vĂ©rifier la prĂ©sence de l’utilisateur en base. Si l’utilisateur portant l’adresse email unique est trouvĂ©, le mot de passe va ĂȘtre vĂ©rifiĂ© Ă  son tour par bcrypt (auth.service.ts).

Si tout se passe bien, la fonction login() va ĂȘtre jouĂ©e avec les informations relatives Ă  l'utilisateur (notamment l'identifiant unique et l'email) qui seront nĂ©cessaires Ă  la gĂ©nĂ©ration d'un jeton d'une validitĂ© de 5 minutes (300s).

Enfin, au retour du endpoint, nous devrions récupérer un jeton JWT (access_token) qui servira à authentifier les autres appels de notre API REST. Pour ce faire, deux derniÚres choses :

  • Ajouter la “guard” JWT sur les endpoints /users/:id (users.controller.ts)
  • Ajouter le Bearer <token> dans le header de(s) l'appel(s)

P R I S M A

Évoquons maintenant la partie concernant les donnĂ©es. À partir de maintenant il va falloir se connecter Ă  notre base de donnĂ©es (prĂ©alablement installĂ© sur notre machine : sudo apt install postgresql), puis l'interroger de maniĂšre REST(ful), Ă  savoir en GET, POST, PATCH, DELETE, etc...

Une derniĂšre fois, un choix cornĂ©lien s’impose, entre :

  • Le driver de la base de donnĂ©es (requĂȘtage de bas niveau)
  • Le “Query Builder” (tel que KnexJS)
  • L’ORM — Object Relational Mapping (requĂȘtage de haut niveau)

NB : À titre d’exemple, avec MongoDB, j’ai l’habitude de dĂ©velopper avec Mongoose (qui est un ODM — Object Document Mapper) pour requĂȘter ma source de donnĂ©es NoSQL. Ayant fait le choix du SQL avec Postgres, il va ĂȘtre indispensable d’utiliser une nouvelle librairie


Pas de panique ! NestJS est lĂ  🎉 Et il dispose dĂ©jĂ  de “connecteurs” pour certaines librairies, dont : Sequelize, TypeORM ou encore Prisma. J’ai prĂ©fĂ©rĂ© Ă©carter Sequelize (malgrĂ© sa popularitĂ©) de la liste des challengers, puisque je souhaite pouvoir rĂ©-utiliser “l’élu” pour un dĂ©veloppement futur avec NoSQL (je ne peux vivre sans Mongo 😅). De ce fait, le seul choix possible n’est autre que Prisma. D’ailleurs, j’ai Ă©tĂ© agrĂ©ablement surpris par ses concepts et son API intuitive ! Il est grand temps d’installer cette nouvelle dĂ©pendance, puis d’initialiser le schĂ©ma de la base de donnĂ©es.

npm install prisma --save-dev
npx prisma init

En exécutant la commande init de Prisma, on devrait voir apparaitre un nouveau dossier à la racine de notre projet, avec un premier brouillon du schéma. Je vous invite donc à le compléter de la sorte :

schema.prisma

Aussi explicite soit-il, le schĂ©ma de base de donnĂ©es va nous permettre d’initialiser notre base PostgreSQL. Clairement, en exĂ©cutant ce dernier (avec la commande migrate), Prisma va transcrire les informations ci-dessus en script SQL, afin de crĂ©er nos tables (avec les bons champs et les bons types) et ajouter nos relations (clĂ©s primaires et Ă©trangĂšres). Il finira par exĂ©cuter la requĂȘte directement en base.

npx prisma migrate dev --name init

NB : À partir de lĂ , notre base est crĂ©Ă©e. Vous pouvez vous y connecter et constater le rĂ©sultat ; ou bien aller jeter un coup d’oeil Ă  la requĂȘte SQL prĂ©sent dans le fichier migration.sql (dans le dossier prisma).

npm i @prisma/client
nest g module prisma
nest g service prisma

La base de donnĂ©es et donc correctement crĂ©Ă©e, mais il nous reste encore Ă  faire le lien entre PostgreSQL (via Prisma) et NestJS. En utilisant l’outil CLI de NestJS (ci-dessus), j’ai gĂ©nĂ©rĂ© deux nouveaux fichiers :

  • prisma.module.ts
  • prisma.service.ts
prisma.module.ts
prisma.service.ts

Une derniĂšre Ă©tape : l’utilisation ! Vous vous souvenez de notre CRUD ci-dessus ? Faisons le Ă©voluer ! DorĂ©navant, plus question de simuler notre base de donnĂ©es, requĂȘtons lĂ  directement. Pour cela, l’API de Prisma est tout simplement magique ! Elle gĂ©nĂšre (grĂące au schĂ©ma de la base de donnĂ©es) les types correspondant aux models, et nous fournit des fonctions intuitives pour interroger notre base SQL. Voyez par vous-mĂȘme le rĂ©sultat avec les fonctions de crĂ©ation et de rĂ©cupĂ©ration d'un utilisateur.

NB : N’oubliez pas d’enregistrer votre nouveau module (prisma.module.ts) dans les imports du module users, sinon impossible de requĂȘter votre source de donnĂ©es...

users.service.ts

NB : Pour des soucis pratiques (réduction de la taille de cet article), je vous présente ici seulement deux des fonctions présentes dans le service, mais vous pouvez consulter le reste du code sur GitHub.

E p i l o g u e

NestJS est une pĂ©pite du dĂ©veloppement Web ! Je me suis Ă©clatĂ© Ă  dĂ©velopper cette nouvelle solution backend en TypeScript. Tout est trĂšs bien structurĂ© ! Par ailleurs, la CLI est d’une grande aide lorsqu’il s’agit de “scaffolder” tout ou partie d’une ressource REST.

Globalement, j’ai Ă©tĂ© agrĂ©ablement surpris par son organisation qui n’est pas sans rappeler les frameworks que sont SpringBoot (en Java) ou encore Django (en Python). Les fichiers sont rangĂ©s Ă  leur place et on s’y retrouve. Comme quoi, le pattern MVC est toujours une valeur sĂ»re


NB : De maniĂšre plus objective, j’ai mĂȘme Ă©tĂ© jusqu’à demander l’avis d’un confrĂšre dĂ©veloppeur spĂ©cialisĂ© en Java, et je vous assure il n’a eu aucun problĂšme Ă  se plonger dans le code (merci TypeScript).

À propos de la sĂ©curitĂ©, NestJS respecte sa part du contrat sans pour autant rĂ©inventer les choses, puisqu’il se repose majoritairement sur la dĂ©pendance Passport pour consolider ses endpoints. Il peut aussi dĂ©finir la sĂ©curitĂ© des headers HTTP en intĂ©grant simplement des middlewares fournis par la librairie Helmet. De mĂȘme pour le chiffrement et le hachage des donnĂ©es, ces concepts ne sont pas une nouveautĂ© et existent Ă©galement dans d’autres frameworks backend.

Je n’ai pas Ă©voquĂ© les tests unitaires prĂ©cĂ©demment, mais ils sont bien prĂ©sents dans ce projet. NestJS fait (lĂ  encore) un bon choix en “abandonnant” le couple Karma / Jasmine au profit de Jest. Il est trĂšs simple de mocker les sources de donnĂ©es Prisma, pour se concentrer sur les appels d’un controller, ou bien sur la fonction d’un service.

En disposant de concepts puissants et d’une documentation bien faite, NestJS s’impose comme une rĂ©fĂ©rence dans le dĂ©veloppement backend en JavaScript. C’est un framework personnalisable (en acceptant le moteur ExpressJS, ou en changeant pour celui de Fastify), et pourtant trĂšs complet avec son lot de plugins : tel que la possibilitĂ© d’ajouter de la documentation OpenAPI via Swagger, ou encore d’ajouter l’abstraction GraphQL Ă  nos appels REST(ful), etc


À l’avenir, je n’hĂ©siterai plus dans mon choix de socle technique pour NodeJS, j’opterai simplement pour NestJS 👍

C o d e S o u r c e

--

--

Damien Chazoule
Damien Chazoule

Written by Damien Chazoule

Développeur FullStack passionné d'informatique depuis plus d'une décennie

No responses yet