Kurs NestJS · Moduł 1: Podstawy NestJS

Witaj w Imperium! Wprowadzenie do NestJS

5 min czytania
W tej lekcji8

Salve, młody legioniście! Witaj w systemie rzymskich technologii! Jestem Konsul Caesar.js, a ty zostaniesz moim młodym legionistą w kampanii przez rozległe prowincje NestJS - najbardziej potężnego frameworka Node.js w Imperium internetu!

Podobnie jak Imperium Romanum potrzebuje solidnej konstrukcji, doświadczonego legionu i dobrze zorganizowanych kohort, tak aplikacja NestJS wymaga przemyślanej architektury, modularności i profesjonalnego podejścia do budowania skalowalnych aplikacji serwerowych.

Czym jest NestJS?

NestJS to progresywny framework Node.js do budowania wydajnych i skalowalnych aplikacji serwerowych. Został zbudowany w TypeScript i wykorzystuje najlepsze wzorce z ekosystemu JavaScript, łącząc je z mocnymi dekoratorami i systemem dependency injection znanym z Angulara.

Wyobraź sobie, że NestJS to nasze potężne Imperium Romanum:

1// Nasze rzymskie kohorty (moduły)
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 {}

Fundamenty NestJS

1. Dekoratory - nasze rzymskie oznaki rangowe

W NestJS dekoratory to jak rzymskie oznaki rangowe - oznaczają rangę i funkcję każdego elementu legionu:

1// Konsul kontroluje całą operację
2@Controller('consul')
3export class ConsulController {
4  // Obsługuje rozkazy dotyczące trybutów
5  @Get('tributes')
6  findTributes() {
7    return 'Trybuty ze wszystkich prowincji!';
8  }
9
10  // Przyjmuje nowych legionistów
11  @Post('legion')
12  recruitLegionary(@Body() newLegionary: CreateLegionaryDto) {
13    return 'Nowy legionista zwerbowany!';
14  }
15}

2. Moduły - struktury naszego Imperium

Każdy moduł to osobna struktura Imperium z określonym przeznaczeniem:

1// Struktura odpowiedzialna za legion
2@Module({
3  controllers: [LegionController],
4  providers: [LegionService],
5  exports: [LegionService],
6})
7export class LegionModule {}
8
9// Struktura odpowiedzialna za trybuty
10@Module({
11  controllers: [TributesController],
12  providers: [TributesService],
13  imports: [DatabaseModule],
14})
15export class TributesModule {}

3. Dependency Injection - łańcuch dowodzenia

System DI w NestJS to jak hierarchia w rzymskim legionie - każdy wie, od kogo ma brać rozkazy:

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}

Instalacja i pierwsze kroki

Aby rozpocząć naszą kampanię w Imperium, potrzebujesz odpowiedniego wyposażenia. Podstawą jest Node.js: generator projektów nest new w NestJS 12 wymaga wersji 22.22.3 lub nowszej z linii 22 albo 24.15 lub nowszej. Sprawdzisz ją poleceniem node -v. Potem wystarczą cztery polecenia:

1# Instalacja NestJS CLI - naszych inżynieryjnych narzędzi
2npm install -g @nestjs/cli
3
4# Stworzenie nowego Imperium (projektu)
5nest new roman-imperium
6
7# Wejście do systemu
8cd roman-imperium
9
10# Podniesienie sztandarów legionu (uruchomienie serwera)
11npm run start:dev

Struktura rzymskiego Imperium (projektu)

1roman-imperium/
2├── src/
3│   ├── main.ts               # Forum Romanum - punkt wejścia
4│   ├── app.module.ts         # Główny Senat
5│   ├── app.controller.ts     # Konsul - główny kontroler
6│   ├── app.service.ts        # Primus Centurio
7│   ├── legion/               # Struktura legionu
8│   │   ├── legion.module.ts
9│   │   ├── legion.controller.ts
10│   │   └── legion.service.ts
11│   ├── tributes/             # Struktura trybutów
12│   └── forum/                # Struktura Forum
13├── test/                     # Poligon treningowy
14├── package.json              # Rejestr imperialny
15└── nest-cli.json             # Instrukcje inżynieryjne

Pierwszy endpoint - sygnał imperialny

Stwórzmy nasz pierwszy endpoint, który będzie sygnalizować obecność Imperium:

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 gotowe do kampanii!';
12  }
13
14  @Get('status')
15  getImperiumStatus() {
16    return {
17      legion: 'Gotowy do kampanii',
18      tributes: 'Zbieranie trwa...',
19      destination: 'Nowe prowincje',
20      consul: 'Caesar.js do usług!',
21    };
22  }
23}

Zalety NestJS dla Imperium

  1. Modularność - każda struktura Imperium ma swoje zadanie
  2. TypeScript - precyzja jak w rzymskim inżynierstwie
  3. Dekoratory - czytelne oznaczenia jak sygnały legionu
  4. Testowanie - sprawdzamy wszystko zanim wyruszymy na kampanię
  5. Dokumentacja - automatyczne mapy naszych prowincji (Swagger)
  6. Skalowalność - od małego castrum po architekturę Imperium

Ćwiczenie praktyczne

Czas na pierwszy test imperialny! Stwórz prosty kontroler, który będzie zarządzał podstawowymi informacjami o legionie:

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: 'Nowy legionista dołączył do legionu!', legionary: newLegionary };
20  }
21}

Następne kampanie

W kolejnych modułach poznamy:

  • Services i Providers - specjalistyczne role w Imperium
  • Middleware - systemy obronne
  • Guards - straże pretoriańskie
  • Interceptors - przechwytywanie sygnałów
  • Pipes - przetwarzanie danych
  • Database integration - magazyny trybutów
  • Authentication - identyfikacja obywateli Imperium
  • WebSockets - komunikacja między prowincjami
  • Testing - przygotowania przed kampanią

Przygotuj się, młody legioniście! Czeka nas fascynująca kampania przez prowincje nowoczesnego web developmentu. NestJS to nie tylko framework - to potężne narzędzie do budowania aplikacji klasy enterprise, które pomoże ci zdobyć najcenniejsze trybuty w świecie programowania!

Pamiętaj: prawdziwy legionista-programista nigdy nie wyrusza bez mapy (dokumentacji) i kompasu (najlepszych praktyk)!

Kod do tej lekcji: src/app.controller.ts
1// NestJS Imperium Romanum - Pierwszy projekt
2import { Controller, Get, Post, Body } from '@nestjs/common';
3import { AppService } from './app.service';
4
5console.log("Salve! Witaj w Imperium Romanum NestJS!");
6console.log("Poznajemy podstawy frameworka dla prawdziwych budowniczych imperium\n");
7
8// ===========================================
9// 1. Podstawowy kontroler - Cesarz systemu
10// ===========================================
11console.log("=== 1. PODSTAWOWY KONTROLER ===");
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: 'Gotowe do kodowania!',
28      status: 'Budujemy drogi przez TypeScript',
29      framework: 'NestJS',
30      timestamp: new Date().toISOString(),
31    };
32  }
33}
34
35// ===========================================
36// 2. Kontroler Legionów - Zarządzanie żołnierzami
37// ===========================================
38console.log("=== 2. KONTROLER LEGIONÓW ===");
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("Lista wszystkich legionistów");
84    return this.legionaries;
85  }
86
87  @Get('commanders')
88  getCommanders(): Legionary[] {
89    console.log("Lista dowódców");
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(`Nowy legionista: ${legionary.name} jako ${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. Kontroler Tributum - Zarządzanie podatkami
134// ===========================================
135console.log("=== 3. KONTROLER 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("Lista wszystkich podatków");
184    return this.tributes;
185  }
186
187  @Get('collected')
188  getCollectedTributes(): Tributum[] {
189    console.log("Zebrane podatki");
190    return this.tributes.filter(t => t.collected);
191  }
192
193  @Get('pending')
194  getPendingTributes(): Tributum[] {
195    console.log("Oczekujące podatki");
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(`Podatek zebrany: ${tribute.name} przez ${collector.collectedBy}`);
216
217    return {
218      success: true,
219      message: `Gratulacje! ${collector.collectedBy} zebrał ${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. Demonstracja API calls
246// ===========================================
247console.log("=== 4. DEMONSTRACJA API ===");
248
249class APIDemo {
250  static async demonstrateAPICalls() {
251    console.log("Demonstracja wywołań API:");
252
253    console.log("GET /legiones - Pobierz legionistów:");
254    const legionaries = new LegionController().getAllLegionaries();
255    console.log(`Znaleziono ${legionaries.length} legionistów`);
256
257    console.log("\nGET /legiones/stats - Statystyki legionów:");
258    const stats = new LegionController().getLegionStats();
259    console.log(`Łącznie milites: ${stats.totalMilites}, Śr. doświadczenie: ${stats.averageExperience}`);
260
261    console.log("\nGET /tributum - Pobierz podatki:");
262    const tributes = new TributumController().getAllTributes();
263    console.log(`Znaleziono ${tributes.length} podatków`);
264
265    console.log("\nGET /tributum/aerarium - Stan skarbca:");
266    const aerarium = new TributumController().getAerarium();
267    console.log(`Wartość aerarium: ${aerarium.aerariumValue} denarii (${aerarium.collectionPercentage}% zebrane)`);
268
269    return {
270      legionaries: legionaries.length,
271      tributes: tributes.length,
272      aerariumValue: aerarium.aerariumValue
273    };
274  }
275}
276
277// ===========================================
278// 5. Uruchomienie demonstracji
279// ===========================================
280console.log("=== 5. URUCHOMIENIE APLIKACJI ===");
281
282async function runImperiumDemo() {
283  try {
284    console.log("Uruchamianie Imperium Romanum NestJS Demo...");
285
286    const results = await APIDemo.demonstrateAPICalls();
287
288    console.log("\nPODSUMOWANIE IMPERIUM:");
289    console.log(`Legioniści: ${results.legionaries} milites`);
290    console.log(`Podatki: ${results.tributes} tributum`);
291    console.log(`Aerarium: ${results.aerariumValue} denarii`);
292    console.log("\nAplikacja NestJS gotowa do podbojów!");
293
294    return results;
295  } catch (error) {
296    console.error("Błąd w Imperium:", error);
297    throw error;
298  }
299}
300
301runImperiumDemo().then(() => {
302  console.log("\nAve Caesar! Demo ukończone pomyślnie!");
303  console.log("Gratulacje - opanowałeś podstawy NestJS!");
304});
305
306export { AppController, LegionController, TributumController };

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Czym jest NestJS?

  2. 2. W jakim języku programowania zbudowany jest NestJS?

Zadania praktyczne w grze

  • Edytor kodu

    Napisz klasę StatusController z dekoratorem @Controller('status') i metodą getStatus() z dekoratorem @Get(). Metoda ma zwracać obiekt z polem status, np. { status: 'Imperium gotowe do kampanii' }.

  • Klikanie w kolejności

    Ułóż kroki tworzenia nowego projektu NestJS w prawidłowej kolejności:

Przydatne artykuły