NestJS course Β· Module 1: NestJS Basics
Welcome to the Empire! Introduction to NestJS
In this lesson8
Salve, young legionary! Welcome to the Roman technology system! I am Consul Caesar.js, and you will become my young legionary in a campaign through the vast provinces of NestJS - the most powerful Node.js framework in the Internet Empire!
Just as the Roman Empire needs solid construction, an experienced legion, and well-organized cohorts, a NestJS application requires well-thought-out architecture, modularity, and a professional approach to building scalable server-side applications.
What is NestJS?
NestJS is a progressive Node.js framework for building efficient and scalable server-side applications. It was built in TypeScript and leverages the best patterns from the JavaScript ecosystem, combining them with powerful decorators and a dependency injection system known from Angular.
Imagine that NestJS is our mighty Roman Empire:
1// Our Roman cohorts (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 {}Fundamentals of NestJS
1. Decorators - our Roman rank insignia
In NestJS, decorators are like Roman rank insignia - they mark the rank and function of each element of the legion:
1// The Consul controls the entire operation
2@Controller('consul')
3export class ConsulController {
4 // Handles orders regarding tributes
5 @Get('tributes')
6 findTributes() {
7 return 'Imperial tributes from all provinces!';
8 }
9
10 // Recruits new legionaries
11 @Post('legion')
12 recruitLegionary(@Body() newLegionary: CreateLegionaryDto) {
13 return 'New legionary recruited successfully!';
14 }
15}2. Modules - structures of our Empire
Each module is a separate structure of the Empire with a specific purpose:
1// Structure responsible for the legion
2@Module({
3 controllers: [LegionController],
4 providers: [LegionService],
5 exports: [LegionService],
6})
7export class LegionModule {}
8
9// Structure responsible for tributes
10@Module({
11 controllers: [TributesController],
12 providers: [TributesService],
13 imports: [DatabaseModule],
14})
15export class TributesModule {}3. Dependency Injection - the chain of command
The DI system in NestJS is like the hierarchy in a Roman legion - everyone knows who to take orders from:
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 and first steps
To begin our campaign in the Empire, you need the proper equipment. The base is Node.js: the nest new project generator in NestJS 12 requires version 22.22.3 or later on the 22 line, or 24.15 or later. You can check it with node -v. Then four commands are enough:
1# Install NestJS CLI - our engineering tools
2npm install -g @nestjs/cli
3
4# Create a new Empire (project)
5nest new roman-imperium
6
7# Enter the system
8cd roman-imperium
9
10# Raise the legion standards (start the server)
11npm run start:devStructure of the Roman Empire (project)
1roman-imperium/
2βββ src/
3β βββ main.ts # Forum Romanum - entry point
4β βββ app.module.ts # The Main Senate
5β βββ app.controller.ts # Consul - main controller
6β βββ app.service.ts # Primus Centurion
7β βββ legion/ # Legion structure
8β β βββ legion.module.ts
9β β βββ legion.controller.ts
10β β βββ legion.service.ts
11β βββ tributes/ # Tributes structure
12β βββ forum/ # Forum structure
13βββ test/ # Training grounds
14βββ package.json # Imperial registry
15βββ nest-cli.json # Engineering instructionsFirst endpoint - imperial signal
Let's create our first endpoint that will signal the presence of the 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 'Roman Imperium is ready for campaign!';
12 }
13
14 @Get('status')
15 getImperiumStatus() {
16 return {
17 legion: 'Ready for campaign',
18 tributes: 'Collecting...',
19 destination: 'New provinces',
20 consul: 'Caesar.js at your service!',
21 };
22 }
23}Advantages of NestJS for the Empire
- Modularity - each structure of the Empire has its own task
- TypeScript - precision like Roman engineering
- Decorators - readable markings like legion signals
- Testing - we check everything before setting out on a campaign
- Documentation - automatic maps of our provinces (Swagger)
- Scalability - from a small castrum to Empire architecture
Practical exercise
Time for the first imperial test! Create a simple controller that will manage basic information about the legion:
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: 'New legionary joined the legion!', legionary: newLegionary };
20 }
21}Upcoming campaigns
In the following modules we will learn about:
- Services and Providers - specialist roles in the Empire
- Middleware - defense systems
- Guards - Praetorian guards
- Interceptors - signal interception
- Pipes - data processing
- Database integration - tribute warehouses
- Authentication - identification of Empire citizens
- WebSockets - communication between provinces
- Testing - preparations before a campaign
Get ready, young legionary! An exciting campaign through the provinces of modern web development awaits us. NestJS is not just a framework - it's a powerful tool for building enterprise-class applications that will help you collect the most valuable tributes in the world of programming!
Remember: a true legionary-programmer never sets out without a map (documentation) and a compass (best practices)!
Code for this lesson: src/app.controller.ts
1// NestJS Imperium Romanum - First Project
2import { Controller, Get, Post, Body } from '@nestjs/common';
3import { AppService } from './app.service';
4
5console.log("Salve! Welcome to Imperium Romanum NestJS!");
6console.log("Learning the basics of a framework for true empire builders\n");
7
8// ===========================================
9// 1. Basic controller - Emperor of the system
10// ===========================================
11console.log("=== 1. BASIC CONTROLLER ===");
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. Legion Controller - Soldier Management
37// ===========================================
38console.log("=== 2. LEGION CONTROLLER ===");
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("List of all legionaries");
84 return this.legionaries;
85 }
86
87 @Get('commanders')
88 getCommanders(): Legionary[] {
89 console.log("List of commanders");
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(`New legionary: ${legionary.name} as ${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. Tributum Controller - Tax Management
134// ===========================================
135console.log("=== 3. TRIBUTUM CONTROLLER ===");
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("List of all taxes");
184 return this.tributes;
185 }
186
187 @Get('collected')
188 getCollectedTributes(): Tributum[] {
189 console.log("Collected taxes");
190 return this.tributes.filter(t => t.collected);
191 }
192
193 @Get('pending')
194 getPendingTributes(): Tributum[] {
195 console.log("Pending taxes");
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(`Tax collected: ${tribute.name} by ${collector.collectedBy}`);
216
217 return {
218 success: true,
219 message: `Congratulations! ${collector.collectedBy} collected ${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. API calls demonstration
246// ===========================================
247console.log("=== 4. API DEMONSTRATION ===");
248
249class APIDemo {
250 static async demonstrateAPICalls() {
251 console.log("API call demonstration:");
252
253 console.log("GET /legiones - Fetch legionaries:");
254 const legionaries = new LegionController().getAllLegionaries();
255 console.log(`Found ${legionaries.length} legionaries`);
256
257 console.log("\nGET /legiones/stats - Legion statistics:");
258 const stats = new LegionController().getLegionStats();
259 console.log(`Total milites: ${stats.totalMilites}, Avg. experience: ${stats.averageExperience}`);
260
261 console.log("\nGET /tributum - Fetch taxes:");
262 const tributes = new TributumController().getAllTributes();
263 console.log(`Found ${tributes.length} taxes`);
264
265 console.log("\nGET /tributum/aerarium - Treasury status:");
266 const aerarium = new TributumController().getAerarium();
267 console.log(`Aerarium value: ${aerarium.aerariumValue} denarii (${aerarium.collectionPercentage}% collected)`);
268
269 return {
270 legionaries: legionaries.length,
271 tributes: tributes.length,
272 aerariumValue: aerarium.aerariumValue
273 };
274 }
275}
276
277// ===========================================
278// 5. Demonstration start
279// ===========================================
280console.log("=== 5. APPLICATION STARTUP ===");
281
282async function runImperiumDemo() {
283 try {
284 console.log("Starting Imperium Romanum NestJS Demo...");
285
286 const results = await APIDemo.demonstrateAPICalls();
287
288 console.log("\nIMPERIUM SUMMARY:");
289 console.log(`Legionaries: ${results.legionaries} milites`);
290 console.log(`Taxes: ${results.tributes} tributum`);
291 console.log(`Aerarium: ${results.aerariumValue} denarii`);
292 console.log("\nNestJS application ready for conquest!");
293
294 return results;
295 } catch (error) {
296 console.error("Error in Imperium:", error);
297 throw error;
298 }
299}
300
301runImperiumDemo().then(() => {
302 console.log("\nAve Caesar! Demo completed successfully!");
303 console.log("Congratulations - you have mastered the basics of NestJS!");
304});
305
306export { AppController, LegionController, TributumController };Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. What is NestJS?
2. What programming language is NestJS built with?
Hands-on tasks in the game
- Code editor
Write a StatusController class with the @Controller('status') decorator and a getStatus() method with the @Get() decorator. The method must return an object with a status field, e.g. { status: 'The Empire is ready for the campaign' }.
- Click in order
Arrange the steps for creating a new NestJS project in the correct order: