Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

Middleware w NestJS

Ave ponownie! Konsul Caesar.js kontynuuje lekcje administracji. Teraz nauczymy się o middleware - strażnikach naszego forum rzymskiego, którzy kontrolują każdy ruch w systemie.

Middleware w NestJS to jak strażnicy przy bramach forum - każdy kto chce wejść do systemu lub przejść między sekcjami musi przejść przez ich kontrolę. Mogą oni sprawdzić tożsamość, zarejestrować wizytę, a nawet odmówić dostępu jeśli coś nie gra.

Czym jest Middleware?

Middleware to funkcje, które wykonują się między żądaniem (request) a odpowiedzią (response). Mają dostęp do obiektów request i response oraz do funkcji

next()
, która przekazuje kontrolę do następnego middleware w kolejce.

1import { Injectable, NestMiddleware } from '@nestjs/common';
2import { Request, Response, NextFunction } from 'express';
3
4@Injectable()
5export class LegionLoggerMiddleware implements NestMiddleware {
6 use(req: Request, res: Response, next: NextFunction) {
7 const timestamp = new Date().toISOString();
8 const method = req.method;
9 const url = req.url;
10
11 console.log(`[FORUM] ${timestamp} - ${method} ${url} - Legionista w systemie!`);
12
13 // Przekazanie kontroli do następnego middleware/kontrolera
14 next();
15 }
16}

Rodzaje Middleware

1. Middleware na poziomie aplikacji

Działa jak główny strażnik przy bramie - sprawdza każdego kto wchodzi do forum:

1// main.ts
2import { NestFactory } from '@nestjs/core';
3import { AppModule } from './app.module';
4
5async function bootstrap() {
6 const app = await NestFactory.create(AppModule);
7
8 // Global middleware
9 app.use((req, res, next) => {
10 console.log('⚔️ Nowy legionista wszedł do systemu!');
11 next();
12 });
13
14 await app.listen(3000);
15}
16bootstrap();

2. Middleware na poziomie modułu

Strażnicy konkretnych sekcji forum:

1import { Module, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
2
3@Module({
4 controllers: [TributesController],
5 providers: [TributesService],
6})
7export class TributesModule {
8 configure(consumer: MiddlewareConsumer) {
9 consumer
10 .apply(LegionLoggerMiddleware)
11 .forRoutes('tributes'); // Tylko dla tras /tributes
12 }
13}

3. Middleware dla konkretnych tras

Strażnicy konkretnych komnat:

1@Module({})
2export class AppModule {
3 configure(consumer: MiddlewareConsumer) {
4 consumer
5 .apply(LegionLoggerMiddleware)
6 .exclude(
7 { path: 'tributes', method: RequestMethod.GET }, // Wyklucz GET /tributes
8 'legion/(.*)', // Wyklucz wszystkie trasy legion
9 )
10 .forRoutes(TributesController); // Zastosuj dla całego kontrolera
11 }
12}

Middleware Authentication

Jeden z najważniejszych strażników sprawdza czy legionista ma prawo dostępu do zasobów:

1@Injectable()
2export class AuthenticationMiddleware implements NestMiddleware {
3 use(req: Request, res: Response, next: NextFunction) {
4 const legionToken = req.headers['legion-token'] as string;
5
6 if (!legionToken) {
7 return res.status(401).json({
8 message: 'Brak tokenu legionisty! Dostęp tylko dla członków legionu!',
9 code: 'NO_LEGION_TOKEN'
10 });
11 }
12
13 // Sprawdzenie czy token jest ważny
14 if (!this.isValidLegionToken(legionToken)) {
15 return res.status(403).json({
16 message: 'Nieprawidłowy token! Czy na pewno jesteś jednym z nas?',
17 code: 'INVALID_LEGION_TOKEN'
18 });
19 }
20
21 // Dodanie informacji o legioniście do request
22 req['legionary'] = this.getLegionaryFromToken(legionToken);
23 next();
24 }
25
26 private isValidLegionToken(token: string): boolean {
27 // Sprawdzenie tokenu w bazie danych legionu
28 const validTokens = ['ave-caesar', 'legio-x', 'tribune-marcus'];
29 return validTokens.includes(token);
30 }
31
32 private getLegionaryFromToken(token: string): any {
33 const legionaries = {
34 'ave-caesar': { name: 'Marcus Aurelius', rank: 'Centurion' },
35 'legio-x': { name: 'Julius Brutus', rank: 'Optio' },
36 'tribune-marcus': { name: 'Titus Flavius', rank: 'Tribunus' }
37 };
38 return legionaries[token];
39 }
40}

CORS Middleware

Kontroluje które prowincje (domeny) mogą komunikować się z naszym forum:

1@Injectable()
2export class CorsMiddleware implements NestMiddleware {
3 use(req: Request, res: Response, next: NextFunction) {
4 const allowedOrigins = [
5 'https://imperium-frontend.com',
6 'https://provincia-map.com',
7 'http://localhost:3000'
8 ];
9
10 const origin = req.headers.origin;
11
12 if (allowedOrigins.includes(origin)) {
13 res.setHeader('Access-Control-Allow-Origin', origin);
14 }
15
16 res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE,OPTIONS');
17 res.setHeader('Access-Control-Allow-Headers', 'Content-Type,legion-token');
18
19 // Obsługa preflight requests
20 if (req.method === 'OPTIONS') {
21 return res.status(200).end();
22 }
23
24 next();
25 }
26}

Request Logging Middleware

Rejestruje wszystkie aktywności w aplikacji:

1@Injectable()
2export class DetailedLoggerMiddleware implements NestMiddleware {
3 use(req: Request, res: Response, next: NextFunction) {
4 const startTime = Date.now();
5 const { method, url, headers, body } = req;
6 const userAgent = headers['user-agent'];
7 const legionToken = headers['legion-token'];
8
9 console.log(`⚔️ [${method}] ${url}`);
10 console.log(`🛡️ Legionista: ${legionToken || 'Nieznany'}`);
11 console.log(`📋 User-Agent: ${userAgent}`);
12
13 if (method === 'POST' || method === 'PUT') {
14 console.log(`📦 Ładunek: ${JSON.stringify(body)}`);
15 }
16
17 // Logowanie odpowiedzi
18 const originalSend = res.send;
19 res.send = function(data) {
20 const endTime = Date.now();
21 const duration = endTime - startTime;
22
23 console.log(`✅ Status: ${res.statusCode}`);
24 console.log(`⏱ Czas: ${duration}ms`);
25 console.log(`📜 Odpowiedź: ${data}`);
26 console.log('---');
27
28 return originalSend.call(this, data);
29 };
30
31 next();
32 }
33}

Rate Limiting Middleware

Zapobiega szturmowi na forum - ogranicza liczbę żądań:

1@Injectable()
2export class RateLimitMiddleware implements NestMiddleware {
3 private requests = new Map<string, { count: number; resetTime: number }>();
4 private readonly MAX_REQUESTS = 100; // Maksymalnie 100 żądań
5 private readonly WINDOW_MS = 15 * 60 * 1000; // Na 15 minut
6
7 use(req: Request, res: Response, next: NextFunction) {
8 const clientId = req.ip || 'unknown';
9 const now = Date.now();
10
11 // Sprawdzenie czy klient już wykonywał żądania
12 let clientData = this.requests.get(clientId);
13
14 if (!clientData || now > clientData.resetTime) {
15 // Nowy klient lub reset okna czasowego
16 clientData = {
17 count: 0,
18 resetTime: now + this.WINDOW_MS
19 };
20 }
21
22 clientData.count++;
23 this.requests.set(clientId, clientData);
24
25 // Sprawdzenie limitu
26 if (clientData.count > this.MAX_REQUESTS) {
27 return res.status(429).json({
28 message: 'Zbyt wiele żądań! Forum nie może obsłużyć takiego natarcia!',
29 retryAfter: Math.ceil((clientData.resetTime - now) / 1000)
30 });
31 }
32
33 // Dodanie headers z informacją o limitach
34 res.setHeader('X-RateLimit-Limit', this.MAX_REQUESTS);
35 res.setHeader('X-RateLimit-Remaining', this.MAX_REQUESTS - clientData.count);
36 res.setHeader('X-RateLimit-Reset', clientData.resetTime);
37
38 next();
39 }
40}

Error Handling Middleware

Obsługuje wyjątki i błędy w aplikacji:

1@Injectable()
2export class ErrorHandlingMiddleware implements NestMiddleware {
3 use(req: Request, res: Response, next: NextFunction) {
4 try {
5 next();
6 } catch (error) {
7 console.error('⚠️ Błąd w systemie!', error);
8
9 // Różne typy błędów
10 if (error.name === 'UnauthorizedError') {
11 return res.status(401).json({
12 message: 'Nie masz uprawnień, młody legioniście!',
13 error: 'Unauthorized'
14 });
15 }
16
17 if (error.name === 'ValidationError') {
18 return res.status(400).json({
19 message: 'Nieprawidłowe dane! Sprawdź swoje raporty!',
20 error: 'Bad Request',
21 details: error.details
22 });
23 }
24
25 // Ogólny błąd serwera
26 return res.status(500).json({
27 message: 'Awaria w systemie! Inżynierowie już pracują nad naprawą!',
28 error: 'Internal Server Error'
29 });
30 }
31 }
32}

Conditional Middleware

Middleware które działa tylko w określonych warunkach:

1@Injectable()
2export class ConditionalMiddleware implements NestMiddleware {
3 use(req: Request, res: Response, next: NextFunction) {
4 // Tylko w godzinach nocnych (dodatkowa ochrona)
5 const hour = new Date().getHours();
6 const isNightTime = hour < 6 || hour > 22;
7
8 if (isNightTime && req.url.includes('/tributes')) {
9 const nightGuardToken = req.headers['night-guard-token'];
10
11 if (!nightGuardToken) {
12 return res.status(403).json({
13 message: 'W nocy tributy są strzeżone! Potrzebujesz specjalnego pozwolenia!',
14 hint: 'Dodaj night-guard-token w nagłówku'
15 });
16 }
17 }
18
19 // Specjalne middleware dla ataków
20 if (req.headers['user-agent']?.includes('AttackBot')) {
21 return res.status(418).json({
22 message: 'Wykryto atak! Balisty gotowe do strzału! 🏹'
23 });
24 }
25
26 next();
27 }
28}

Rejestracja Middleware w Module

1@Module({
2 controllers: [TributesController, LegionController],
3 providers: [TributesService, LegionService],
4})
5export class ImperiumModule {
6 configure(consumer: MiddlewareConsumer) {
7 consumer
8 // Logger dla wszystkich tras
9 .apply(DetailedLoggerMiddleware)
10 .forRoutes('*')
11
12 // Rate limiting dla publicznych API
13 .apply(RateLimitMiddleware)
14 .forRoutes(
15 { path: 'tributes', method: RequestMethod.GET },
16 { path: 'legion', method: RequestMethod.GET }
17 )
18
19 // Autentyfikacja dla chronionych tras
20 .apply(AuthenticationMiddleware)
21 .exclude('auth/login', 'auth/register')
22 .forRoutes(
23 { path: 'tributes', method: RequestMethod.POST },
24 { path: 'tributes', method: RequestMethod.PUT },
25 { path: 'tributes', method: RequestMethod.DELETE },
26 TributesController
27 )
28
29 // Dodatkowa ochrona nocna
30 .apply(ConditionalMiddleware)
31 .forRoutes('tributes/*');
32 }
33}
Ir a CodeWorlds