Cours NestJS · Module 1: Bases de NestJS
Bienvenue dans l'Empire ! Introduction à NestJS
Dans cette leçon8
Salve, jeune légionnaire ! Bienvenue dans le système technologique romain ! Je suis le consul Caesar.js, et tu deviendras mon jeune légionnaire dans une campagne à travers les vastes provinces de NestJS - le framework Node.js le plus puissant de l'Empire d'Internet !
Tout comme l'Empire romain a besoin d'une construction solide, d'une légion expérimentée et de cohortes bien organisées, une application NestJS nécessite une architecture bien pensée, de la modularité et une approche professionnelle pour construire des applications côté serveur évolutives.
Qu'est-ce que NestJS ?
NestJS est un framework Node.js progressif pour construire des applications côté serveur efficaces et évolutives. Il a été construit en TypeScript et tire parti des meilleurs patrons de l'écosystème JavaScript, en les combinant avec de puissants décorateurs et un système d'injection de dépendances connu d'Angular.
Imagine que NestJS est notre puissant Empire romain :
1// Nos cohortes romaines (modules)
2import { Module } from '@nestjs/common';
3import { LegionModule } from './legion/legion.module';
4import { TributesModule } from './tributes/tributes.module';
5import { ForumModule } from './forum/forum.module';
6
7@Module({
8 imports: [LegionModule, TributesModule, ForumModule],
9})
10export class ImperiumModule {}Fondamentaux de NestJS
1. Les décorateurs - nos insignes de rang romains
Dans NestJS, les décorateurs sont comme les insignes de rang romains - ils marquent le rang et la fonction de chaque élément de la légion :
1// Le consul contrôle toute l'opération
2@Controller('consul')
3export class ConsulController {
4 // Gère les ordres concernant les tributs
5 @Get('tributes')
6 findTributes() {
7 return 'Les tributs de toutes les provinces !';
8 }
9
10 // Recrute de nouveaux légionnaires
11 @Post('legion')
12 recruitLegionary(@Body() newLegionary: CreateLegionaryDto) {
13 return 'Nouveau légionnaire recruté !';
14 }
15}2. Les modules - structures de notre Empire
Chaque module est une structure distincte de l'Empire avec un objectif précis :
1// Structure responsable de la légion
2@Module({
3 controllers: [LegionController],
4 providers: [LegionService],
5 exports: [LegionService],
6})
7export class LegionModule {}
8
9// Structure responsable des tributs
10@Module({
11 controllers: [TributesController],
12 providers: [TributesService],
13 imports: [DatabaseModule],
14})
15export class TributesModule {}3. L'injection de dépendances - la chaîne de commandement
Le système de DI dans NestJS est comme la hiérarchie dans une légion romaine - chacun sait de qui recevoir des ordres :
1@Injectable()
2export class TributesService {
3 constructor(
4 private readonly mapService: MapService,
5 private readonly collectorService: CollectorService,
6 ) {}
7
8 async findTributes(province: string) {
9 const location = await this.mapService.getProvince(province);
10 return this.collectorService.collect(location);
11 }
12}Installation et premiers pas
Pour commencer notre campagne dans l'Empire, il te faut l'équipement approprié. La base, c'est Node.js : le générateur de projets nest new de NestJS 12 exige la version 22.22.3 ou plus récente de la ligne 22, ou la 24.15 ou plus récente. Tu peux la vérifier avec la commande node -v. Ensuite, quatre commandes suffisent :
1# Installe le NestJS CLI - nos outils d'ingénierie
2npm install -g @nestjs/cli
3
4# Crée un nouvel Empire (projet)
5nest new roman-imperium
6
7# Entre dans le système
8cd roman-imperium
9
10# Lève les étendards de la légion (démarre le serveur)
11npm run start:devStructure de l'Empire romain (projet)
1roman-imperium/
2├── src/
3│ ├── main.ts # Forum Romanum - point d'entrée
4│ ├── app.module.ts # Le Sénat principal
5│ ├── app.controller.ts # Consul - contrôleur principal
6│ ├── app.service.ts # Primus Centurion
7│ ├── legion/ # Structure de la légion
8│ │ ├── legion.module.ts
9│ │ ├── legion.controller.ts
10│ │ └── legion.service.ts
11│ ├── tributes/ # Structure des tributs
12│ └── forum/ # Structure du forum
13├── test/ # Terrain d'entraînement
14├── package.json # Registre impérial
15└── nest-cli.json # Instructions d'ingénieriePremier endpoint - signal impérial
Créons notre premier endpoint qui signalera la présence de l'Empire :
1// app.controller.ts
2import { Controller, Get } from '@nestjs/common';
3import { AppService } from './app.service';
4
5@Controller()
6export class AppController {
7 constructor(private readonly appService: AppService) {}
8
9 @Get()
10 getSignal(): string {
11 return 'Imperium prêt pour la campagne !';
12 }
13
14 @Get('status')
15 getImperiumStatus() {
16 return {
17 legion: 'Prête pour la campagne',
18 tributes: 'Collecte en cours...',
19 destination: 'Nouvelles provinces',
20 consul: 'Caesar.js à votre service !',
21 };
22 }
23}Avantages de NestJS pour l'Empire
- Modularité - chaque structure de l'Empire a sa propre tâche
- TypeScript - une précision digne de l'ingénierie romaine
- Décorateurs - des marquages lisibles comme les signaux de la légion
- Tests - nous vérifions tout avant de partir en campagne
- Documentation - des cartes automatiques de nos provinces (Swagger)
- Évolutivité - d'un petit castrum jusqu'à l'architecture de l'Empire
Exercice pratique
L'heure du premier test impérial ! Crée un contrôleur simple qui gérera des informations de base sur la légion :
1// legion.controller.ts
2import { Controller, Get, Post, Body } from '@nestjs/common';
3
4@Controller('legion')
5export class LegionController {
6 private legionaries = [
7 { name: 'Marcus Aurelius', role: 'Centurion', experience: 10 },
8 { name: 'Julius Brutus', role: 'Engineer', experience: 5 },
9 ];
10
11 @Get()
12 getAllLegionaries() {
13 return this.legionaries;
14 }
15
16 @Post()
17 addLegionary(@Body() newLegionary: any) {
18 this.legionaries.push(newLegionary);
19 return { message: 'Un nouveau légionnaire a rejoint la légion !', legionary: newLegionary };
20 }
21}Campagnes à venir
Dans les modules suivants, nous découvrirons :
- Services et Providers - les rôles de spécialistes dans l'Empire
- Middleware - les systèmes de défense
- Guards - les gardes prétoriens
- Interceptors - l'interception des signaux
- Pipes - le traitement des données
- Intégration de base de données - les entrepôts de tributs
- Authentification - l'identification des citoyens de l'Empire
- WebSockets - la communication entre provinces
- Tests - les préparatifs avant une campagne
Prépare-toi, jeune légionnaire ! Une campagne passionnante à travers les provinces du développement web moderne nous attend. NestJS n'est pas qu'un framework - c'est un outil puissant pour construire des applications de classe entreprise qui t'aidera à collecter les tributs les plus précieux du monde de la programmation !
Souviens-toi : un vrai légionnaire-programmeur ne part jamais sans une carte (documentation) et une boussole (bonnes pratiques) !
Code de cette leçon : src/app.controller.ts
1// NestJS Imperium Romanum - Premier projet
2import { Controller, Get, Post, Body } from '@nestjs/common';
3import { AppService } from './app.service';
4
5console.log("Salve ! Bienvenue dans l'Imperium Romanum NestJS !");
6console.log("Apprends les bases d'un framework pour les vrais bâtisseurs d'empire\n");
7
8// ===========================================
9// 1. Contrôleur de base - Empereur du système
10// ===========================================
11console.log("=== 1. CONTRÔLEUR DE BASE ===");
12
13@Controller()
14export class AppController {
15 constructor(private readonly appService: AppService) {}
16
17 @Get()
18 getWelcome(): string {
19 return this.appService.getWelcome();
20 }
21
22 @Get('status')
23 getImperiumStatus() {
24 return {
25 imperium: 'Roma Aeterna NestJS',
26 caesar: 'Augustus.js',
27 legiones: 'Ready for coding!',
28 status: 'We build roads through TypeScript',
29 framework: 'NestJS',
30 timestamp: new Date().toISOString(),
31 };
32 }
33}
34
35// ===========================================
36// 2. Contrôleur de légion - Gestion des soldats
37// ===========================================
38console.log("=== 2. CONTRÔLEUR DE LÉGION ===");
39
40interface Legionary {
41 id: number;
42 name: string;
43 rank: string;
44 experience: number;
45 legio: string;
46}
47
48@Controller('legiones')
49export class LegionController {
50 private legionaries: Legionary[] = [
51 {
52 id: 1,
53 name: 'Marcus Aurelius',
54 rank: 'Legatus',
55 experience: 10,
56 legio: 'Legio X Gemina'
57 },
58 {
59 id: 2,
60 name: 'Gaius Julius',
61 rank: 'Centurio',
62 experience: 8,
63 legio: 'Legio III Augusta'
64 },
65 {
66 id: 3,
67 name: 'Lucius Scipio',
68 rank: 'Optio',
69 experience: 6,
70 legio: 'Legio IX Hispana'
71 },
72 {
73 id: 4,
74 name: 'Titus Flavius',
75 rank: 'Miles',
76 experience: 5,
77 legio: 'Legio XII Fulminata'
78 }
79 ];
80
81 @Get()
82 getAllLegionaries(): Legionary[] {
83 console.log("Liste de tous les légionnaires");
84 return this.legionaries;
85 }
86
87 @Get('commanders')
88 getCommanders(): Legionary[] {
89 console.log("Liste des commandants");
90 return this.legionaries.filter(l => l.rank === 'Legatus' || l.rank === 'Centurio');
91 }
92
93 @Post()
94 recruitLegionary(@Body() newLegionary: Omit<Legionary, 'id'>): Legionary {
95 const legionary: Legionary = {
96 id: this.getNextId(),
97 ...newLegionary,
98 };
99
100 this.legionaries.push(legionary);
101 console.log(`Nouveau légionnaire : ${legionary.name} en tant que ${legionary.rank}`);
102
103 return legionary;
104 }
105
106 @Get('stats')
107 getLegionStats() {
108 const totalExperience = this.legionaries.reduce((sum, l) => sum + l.experience, 0);
109 const avgExperience = totalExperience / this.legionaries.length;
110
111 const rankCounts = this.legionaries.reduce((counts, l) => {
112 counts[l.rank] = (counts[l.rank] || 0) + 1;
113 return counts;
114 }, {} as Record<string, number>);
115
116 return {
117 totalMilites: this.legionaries.length,
118 totalExperience,
119 averageExperience: Math.round(avgExperience * 10) / 10,
120 rankDistribution: rankCounts,
121 mostExperienced: this.legionaries.reduce((prev, current) =>
122 prev.experience > current.experience ? prev : current
123 ),
124 };
125 }
126
127 private getNextId(): number {
128 return Math.max(...this.legionaries.map(l => l.id), 0) + 1;
129 }
130}
131
132// ===========================================
133// 3. Contrôleur de tributum - Gestion des impôts
134// ===========================================
135console.log("=== 3. CONTRÔLEUR DE TRIBUTUM ===");
136
137interface Tributum {
138 id: number;
139 name: string;
140 value: number;
141 provincia: string;
142 collected: boolean;
143 collectedBy?: string;
144}
145
146@Controller('tributum')
147export class TributumController {
148 private tributes: Tributum[] = [
149 {
150 id: 1,
151 name: 'Aurum Galliae',
152 value: 50000,
153 provincia: 'Gallia',
154 collected: true,
155 collectedBy: 'Marcus Aurelius'
156 },
157 {
158 id: 2,
159 name: 'Argentum Hispaniae',
160 value: 75000,
161 provincia: 'Hispania',
162 collected: false
163 },
164 {
165 id: 3,
166 name: 'Aes Britanniae',
167 value: 100000,
168 provincia: 'Britannia',
169 collected: true,
170 collectedBy: 'Gaius Julius'
171 },
172 {
173 id: 4,
174 name: 'Triticum Aegypti',
175 value: 25000,
176 provincia: 'Aegyptus',
177 collected: false
178 }
179 ];
180
181 @Get()
182 getAllTributes(): Tributum[] {
183 console.log("Liste de tous les impôts");
184 return this.tributes;
185 }
186
187 @Get('collected')
188 getCollectedTributes(): Tributum[] {
189 console.log("Impôts collectés");
190 return this.tributes.filter(t => t.collected);
191 }
192
193 @Get('pending')
194 getPendingTributes(): Tributum[] {
195 console.log("Impôts en attente");
196 return this.tributes.filter(t => !t.collected);
197 }
198
199 @Post('collect/:id')
200 collectTribute(@Body() collector: { collectedBy: string }) {
201 const tributeId = 2;
202 const tribute = this.tributes.find(t => t.id === tributeId);
203
204 if (!tribute) {
205 return { success: false, message: 'Tributum non inventum!' };
206 }
207
208 if (tribute.collected) {
209 return { success: false, message: 'Tributum iam collectum!' };
210 }
211
212 tribute.collected = true;
213 tribute.collectedBy = collector.collectedBy;
214
215 console.log(`Impôt collecté : ${tribute.name} par ${collector.collectedBy}`);
216
217 return {
218 success: true,
219 message: `Félicitations ! ${collector.collectedBy} a collecté ${tribute.name} !`,
220 tribute,
221 reward: tribute.value
222 };
223 }
224
225 @Get('aerarium')
226 getAerarium() {
227 const collectedValue = this.tributes
228 .filter(t => t.collected)
229 .reduce((sum, t) => sum + t.value, 0);
230
231 const pendingValue = this.tributes
232 .filter(t => !t.collected)
233 .reduce((sum, t) => sum + t.value, 0);
234
235 return {
236 aerariumValue: collectedValue,
237 pendingValue,
238 totalValue: collectedValue + pendingValue,
239 collectionPercentage: Math.round((collectedValue / (collectedValue + pendingValue)) * 100)
240 };
241 }
242}
243
244// ===========================================
245// 4. Démonstration des appels d'API
246// ===========================================
247console.log("=== 4. DÉMONSTRATION DE L'API ===");
248
249class APIDemo {
250 static async demonstrateAPICalls() {
251 console.log("Démonstration des appels d'API :");
252
253 console.log("GET /legiones - Récupère les légionnaires :");
254 const legionaries = new LegionController().getAllLegionaries();
255 console.log(`${legionaries.length} légionnaires trouvés`);
256
257 console.log("\nGET /legiones/stats - Statistiques de la légion :");
258 const stats = new LegionController().getLegionStats();
259 console.log(`Total des milites : ${stats.totalMilites}, Expérience moy. : ${stats.averageExperience}`);
260
261 console.log("\nGET /tributum - Récupère les impôts :");
262 const tributes = new TributumController().getAllTributes();
263 console.log(`${tributes.length} impôts trouvés`);
264
265 console.log("\nGET /tributum/aerarium - État du trésor :");
266 const aerarium = new TributumController().getAerarium();
267 console.log(`Valeur de l'aerarium : ${aerarium.aerariumValue} deniers (${aerarium.collectionPercentage}% collecté)`);
268
269 return {
270 legionaries: legionaries.length,
271 tributes: tributes.length,
272 aerariumValue: aerarium.aerariumValue
273 };
274 }
275}
276
277// ===========================================
278// 5. Démarrage de la démonstration
279// ===========================================
280console.log("=== 5. DÉMARRAGE DE L'APPLICATION ===");
281
282async function runImperiumDemo() {
283 try {
284 console.log("Démarrage de la démo Imperium Romanum NestJS...");
285
286 const results = await APIDemo.demonstrateAPICalls();
287
288 console.log("\nRÉSUMÉ DE L'IMPERIUM :");
289 console.log(`Légionnaires : ${results.legionaries} milites`);
290 console.log(`Impôts : ${results.tributes} tributum`);
291 console.log(`Aerarium : ${results.aerariumValue} deniers`);
292 console.log("\nApplication NestJS prête pour la conquête !");
293
294 return results;
295 } catch (error) {
296 console.error("Erreur dans l'Imperium :", error);
297 throw error;
298 }
299}
300
301runImperiumDemo().then(() => {
302 console.log("\nAve Caesar ! Démo terminée avec succès !");
303 console.log("Félicitations - tu maîtrises les bases de NestJS !");
304});
305
306export { AppController, LegionController, TributumController };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. Qu'est-ce que NestJS ?
2. Avec quel langage de programmation NestJS est-il construit ?
Tâches pratiques dans le jeu
- Éditeur de code
Écris une classe StatusController avec le décorateur @Controller('status') et une méthode getStatus() avec le décorateur @Get(). La méthode doit renvoyer un objet avec un champ status, par exemple { status: 'Empire prêt pour la campagne' }.
- Clique dans l'ordre
Remets dans le bon ordre les étapes de création d'un nouveau projet NestJS :