Cours NestJS · Module 9: Déploiement et infrastructure

Deployment - conduire le fort au champ de bataille

6 min de lecture
Dans cette leçon6

Un camp d'entraînement pardonne tout. Le rempart est bas, puisque personne ne l'assaille ; les vivres arrivent chaque jour ; et si quelque chose s'effondre, on le relève après le repas. Un fort sur la frontière ne pardonne rien - non parce qu'on l'a bâti autrement, mais parce que personne ne se tient à côté pour corriger.

Le déploiement, c'est cet instant du départ. L'application que tu lançais par yarn start:dev doit désormais fonctionner seule, des jours durant, pour des gens que tu ne connais pas. Cette leçon porte sur les quatre choses à faire avant que cela n'arrive.

nest build - la compilation

En mode développement, NestJS traduit le TypeScript au fil de l'eau, à chaque enregistrement de fichier. En production, nous traduisons une fois, à l'avance :

1{
2  "scripts": {
3    "build": "nest build",
4    "start:dev": "nest start --watch",
5    "start:prod": "node dist/main"
6  }
7}

La commande nest build compile le TypeScript en JavaScript dans le répertoire dist/ - et rien d'autre. Elle ne génère pas la documentation Swagger (celle-ci s'ajoute à part, par des décorateurs et SwaggerModule), n'installe pas les dépendances depuis package.json (c'est le rôle de yarn install), ne démarre pas le mode watch (c'est start:dev avec l'option --watch).

Après la compilation, tu lances node dist/main - du Node.js ordinaire sur un fichier .js ordinaire. Sur un serveur de production, TypeScript n'est plus nécessaire : il n'y a plus rien à traduire.

NODE_ENV - une chaîne, bien des conséquences

NODE_ENV est une variable d'environnement que lisent presque toutes les bibliothèques de l'écosystème Node.js. Définir NODE_ENV=production active les optimisations de production et désactive la journalisation détaillée.

C'est tout ce que cela fait. Cela n'exécute pas les tests automatisés - yarn test s'en charge. Cela n'active pas le mode hot-reload ; au contraire, le hot-reload relève du développement. Cela ne génère pas la documentation de l'API. Changer une chaîne n'ajoute aucune fonctionnalité à une application - cela bascule seulement ce qui s'y trouve déjà du mode « aide le développeur » au mode « sers le trafic ».

Les effets peuvent être étonnamment vastes : les bibliothèques sautent des vérifications coûteuses, la mise en cache des gabarits s'active, et les traces de pile cessent d'apparaître dans les réponses d'erreur - car l'utilisateur n'en a aucun usage, et l'attaquant un très grand.

Les variables d'environnement - ce qui distingue le fort du camp

NODE_ENV n'en est qu'une parmi d'autres. L'adresse de la base de données, le secret de signature des tokens, la clé de la passerelle de paiement - ce sont autant de variables d'environnement : des valeurs fournies à l'application depuis l'extérieur, au lieu d'être inscrites dans le code.

1NODE_ENV=production
2PORT=3000
3DATABASE_URL=postgres://legion:motdepasse@db.limes.internal:5432/tributes
4JWT_SECRET=change-moi-avant-le-depart

La raison est pratique : le même dist/ compilé doit tourner sur ta machine, sur l'environnement de test et en production. Seul le jeu de variables les distingue. Si l'adresse de la base siégeait dans le code, chaque environnement exigerait sa propre compilation - et ce que tu as testé ne serait alors pas ce que tu exécutes.

Voilà pourquoi la configuration des variables est la première étape de la préparation, avant toute compilation.

Un main.ts de production

Le fichier main.ts démarre l'application. Dans sa version de développement, il tient d'ordinaire en deux lignes ; en production, on lui ajoute quatre choses, dans cet ordre :

1async function bootstrap() {
2  const app = await NestFactory.create(AppModule);
3
4  app.use(helmet());
5  app.use(compression());
6  app.enableCors({ origin: 'https://legion.imperium.rome' });
7
8  app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
9
10  const port = process.env.PORT || 3000;
11  await app.listen(port);
12}

L'ordre n'a rien de fortuit : const app = await NestFactory.create(AppModule) doit venir en premier, car l'objet application n'existe qu'ensuite ; app.use(helmet()) et les autres middlewares se posent avant tout service du trafic ; app.useGlobalPipes(new ValidationPipe()) enregistre la validation ; await app.listen(port) ouvre le port, et dès cet instant l'application accepte des requêtes. Tout ce que tu écris après listen s'exécute serveur déjà levé.

Chacun de ces quatre éléments répond d'autre chose :

  • helmet définit les en-têtes de sécurité HTTP - Content-Security-Policy (CSP), X-Frame-Options, Strict-Transport-Security et une dizaine d'autres. Il ne chiffre pas la base de données, ne compresse pas les fichiers statiques et ne gère pas les sessions utilisateur ; c'est un casque pour les réponses, non pour les données.
  • compression emballe les réponses en gzip avant leur départ sur le réseau. Un JSON de quelques centaines de kilooctets peut retomber à quelques dizaines.
  • enableCors tranche depuis quels sites un navigateur peut appeler ton API. En production, on nomme une adresse précise, non une étoile.
  • ValidationPipe est la sentinelle des données d'entrée que tu connais déjà, activée ici globalement.

Quatre étapes

Toute la route vers la production s'ordonne d'une manière qu'on ne peut intervertir :

  1. Configurer les variables d'environnement - car elles décident à quelle base l'application se connecte.
  2. Ajouter helmet, compression et CORS - car ce sont des modifications du code, et le code va être figé.
  3. Exécuter nest build - le répertoire dist/ apparaît, immuable dès lors.
  4. Exécuter node dist/main en production - ce que tu as compilé démarre, avec les variables qu'il trouve.

Intervertir les étapes deux et trois est l'erreur la plus fréquente de cette liste : un dist/ compilé ignore les changements écrits après la compilation. L'application se lèvera, ne signalera aucune erreur, et tournera sans en-têtes de sécurité.

Récapitulatif

Le fort part pour la frontière :

  • nest build compile le TypeScript en JavaScript dans le répertoire dist/ - il ne génère pas Swagger, n'installe pas les dépendances, ne démarre pas le mode watch,
  • en production, tu lances node dist/main ; TypeScript n'y est plus nécessaire,
  • NODE_ENV=production active les optimisations de production et désactive la journalisation détaillée - cela n'exécute pas les tests, n'active pas le hot-reload, ne génère pas la documentation,
  • les variables d'environnement permettent d'exécuter le même répertoire compilé sur tous les environnements - d'où leur configuration en premier,
  • l'ordre dans main.ts : const app = await NestFactory.create(AppModule) → app.use(helmet()) → app.useGlobalPipes(new ValidationPipe()) → await app.listen(port),
  • helmet définit les en-têtes de sécurité HTTP (CSP, X-Frame-Options et autres) - il ne chiffre pas la base, ne compresse pas les fichiers statiques, ne gère pas les sessions,
  • compression emballe les réponses en gzip, enableCors nomme les origines de requêtes autorisées,
  • quatre étapes de préparation : variables d'environnement → helmet, compression, CORS → nest build → node dist/main.

Dans la prochaine leçon, nous enfermerons le fort dans un conteneur, afin que son intérieur soit identique sur toute machine. En attendant, retiens ceci : la production n'est pas une version plus difficile du développement. C'est la même application, sans personne pour corriger.

Code de cette leçon : src/deployment-intro.ts
1// Déploiement - emmener le fort en pleine mer
2import { NestFactory } from '@nestjs/core';
3import { ValidationPipe, Logger } from '@nestjs/common';
4
5// 1. Configuration de production main.ts
6async function bootstrap() {
7  const logger = new Logger('Bootstrap');
8
9  // Nous créons l'application avec journalisation limitée
10  const app = await NestFactory.create(AppModule, {
11    logger: ['error', 'warn', 'log'],
12  });
13
14  // Sécurité - Helmet
15  // app.use(helmet());
16
17  // Compression des réponses - réduit la taille de 60 à 80 %
18  // app.use(compression());
19
20  // CORS - contrôle d'accès
21  app.enableCors({
22    origin: process.env.CORS_ORIGIN || 'http://localhost:3000',
23    credentials: true,
24  });
25
26  // Validation globale
27  app.useGlobalPipes(new ValidationPipe({
28    whitelist: true,           // Supprimer les champs inconnus
29    forbidNonWhitelisted: true, // Erreur sur les champs inconnus
30    transform: true,           // Transformation automatique des types
31  }));
32
33  // Préfixe API
34  app.setGlobalPrefix('api/v1');
35
36  // Graceful Shutdown - close connections before shutting down
37  app.enableShutdownHooks();
38
39  const port = process.env.PORT || 3000;
40  await app.listen(port, '0.0.0.0');
41
42  logger.log(`Application running on port ${port}`);
43  logger.log(`Environment: ${process.env.NODE_ENV}`);
44}
45
46// 2. Différences entre les environnements
47// Development:  hot-reload, debug, verbose logging
48// Staging:      production-like, test data
49// Production:   optimisée, logs limités, sécurité
50
51// 3. Scripts dans package.json
52const scripts = {
53  'start:dev': 'nest start --watch',
54  'start:debug': 'nest start --debug --watch',
55  'build': 'nest build',
56  'start:prod': 'node dist/main',
57  'test': 'jest',
58  'test:cov': 'jest --coverage',
59  'test:e2e': 'jest --config ./test/jest-e2e.json',
60  'lint': 'eslint "{src,test}/**/*.ts" --fix',
61};
62
63// 4. Pre-deployment checklist
64const checklist = [
65  'Tests réussis (unit + e2e)',
66  'Environment variables set',
67  'Base de données migrée',
68  'Secrets sécurisés (pas dans le code !)',
69  'CORS configuré',
70  'Health check endpoint prêt',
71  'Monitoring activé',
72  'Backup strategy prête',
73];
74

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. La commande nest build dans NestJS :

  2. 2. Définir NODE_ENV=production dans une application NestJS :

Voici 2 des 3 questions de cette leçon. Réponds aux autres dans le jeu.

Tâches pratiques dans le jeu

  • Éditeur de code

    Configure main.ts avec helmet, compression, CORS et ValidationPipe pour la production

  • Tri vertical

    Ordonne les étapes de préparation d'une application NestJS pour la production :

  • Clique dans l'ordre

    Ordonne les éléments de configuration de main.ts dans le bon ordre :