NestJS course Β· Module 1: NestJS Basics

Welcome to the Empire! Introduction to NestJS

5 min read
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:dev

Structure 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 instructions

First 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

  1. Modularity - each structure of the Empire has its own task
  2. TypeScript - precision like Roman engineering
  3. Decorators - readable markings like legion signals
  4. Testing - we check everything before setting out on a campaign
  5. Documentation - automatic maps of our provinces (Swagger)
  6. 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. 1. What is NestJS?

  2. 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:

Useful articles