NestJS oferuje wiele zaawansowanych narzędzi i funkcjonalności, które pozwalają na tworzenie skalowalnych i nowoczesnych aplikacji serwerowych. W tym wpisie na blogu omówimy trzy zaawansowane koncepcje w NestJS: interceptorzy, komunikacja w czasie rzeczywistym z WebSockets oraz asynchroniczne taski i harmonogramowanie. Dzięki temu dowiesz się, jak wzbogacić swoją aplikację o nowe możliwości, które mogą znacznie poprawić wydajność, użyteczność i nowoczesność aplikacji.
Interceptorzy w NestJS
Czym są interceptorzy?
Interceptorzy są specjalnym rodzajem middleware, który działa w trakcie przetwarzania zapytania przez serwer. W odróżnieniu od typowego middleware, interceptorzy mają dostęp zarówno do danych przed przetworzeniem przez kontroler, jak i po ich przetworzeniu, co pozwala m.in. na modyfikowanie odpowiedzi, logowanie i obsługę czasu przetwarzania. Ich użycie można porównać do tzw. „haków”, które pozwalają wchodzić w różne etapy przetwarzania żądania/odpowiedzi.
Tworzenie własnego interceptora
Poniżej przedstawiamy przykładowy interceptor, który loguje czas przetwarzania zapytań:
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LogTimeInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable {
const start = Date.now();
return next
.handle()
.pipe(
tap(() =>
console.log(
`Request took ${Date.now() - start}ms`,
),
),
);
}
}
Interceptor LogTimeInterceptor mierzy czas trwania żądania. ExecutionContext pozwala uzyskać szczegóły dotyczące aktualnego kontekstu, a CallHandler umożliwia przekazywanie kontroli do kolejnych interceptorów lub docelowego kontrolera.
Rejestracja interceptora
Aby zarejestrować interceptor, możemy go użyć w kontrolerze lub na poziomie całej aplikacji. Przykład na poziomie kontrolera:
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { LogTimeInterceptor } from './log-time.interceptor';
@Controller('uzytkownicy')
@UseInterceptors(LogTimeInterceptor)
export class UzytkownikController {
@Get()
znajdzWszystkich() {
return [
{ imie: 'Jan', nazwisko: 'Kowalski' },
{ imie: 'Anna', nazwisko: 'Nowak' },
];
}
}
W ten sposób LogTimeInterceptor zostanie wywołany za każdym razem, gdy ktoś wywoła endpoint GET /uzytkownicy.
Modyfikacja odpowiedzi
Interceptorzy mogą być także używani do modyfikacji odpowiedzi. Poniżej przedstawiamy przykład, który dodaje dodatkową informację do odpowiedzi:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class TransformResponseInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable {
return next.handle().pipe(
map((data) => ({
status: 'success',
data,
})),
);
}
}
Powyższy interceptor opakowuje każdą odpowiedź w obiekt zawierający pole status z wartością success oraz pole data zawierające właściwą odpowiedź.
Eventy i WebSockets
Komunikacja w czasie rzeczywistym z WebSockets
NestJS posiada doskonałe wsparcie dla komunikacji w czasie rzeczywistym, oferując moduł WebSocket Gateway. Dzięki WebSocket możemy przesyłać dane między serwerem a klientem natychmiast po ich zmianie, co pozwala na tworzenie dynamicznych aplikacji (np. chaty, gry, monitorowanie na żywo).
Instalacja WebSocket Gateway
Aby korzystać z WebSocket Gateway w NestJS, najpierw musimy zainstalować odpowiednie zależności:
npm install @nestjs/websockets @nestjs/platform-socket.io
Tworzenie prostego WebSocket Gateway
Stwórzmy prosty WebSocket Gateway, który pozwoli nam na emitowanie i nasłuchiwanie wiadomości.
import { WebSocketGateway, SubscribeMessage, MessageBody, WebSocketServer } from '@nestjs/websockets';
import { Server } from 'socket.io';
@WebSocketGateway()
export class ChatGateway {
@WebSocketServer()
server: Server;
@SubscribeMessage('sendMessage')
handleMessage(@MessageBody() message: string): void {
console.log(`Received message: ${message}`);
this.server.emit('newMessage', message);
}
}
@WebSocketGateway()– Dekorator oznaczający klasę jako WebSocket Gateway.@WebSocketServer()– Umożliwia dostęp do serwera WebSocket (Serverzsocket.io).@SubscribeMessage('sendMessage')– Dekorator definiujący, jak obsługiwać wiadomości od klientów.
W powyższym przykładzie metoda handleMessage nasłuchuje na wiadomości sendMessage od klienta i emituje nową wiadomość newMessage do wszystkich klientów.
Klient nasłuchujący
Klient (np. przeglądarka) może łączyć się z naszym WebSocket Gateway za pomocą biblioteki socket.io-client. Przykład (JavaScript):
import { io } from "socket.io-client";
const socket = io('http://localhost:3000');
socket.on('newMessage', (message) => {
console.log(`Received new message: ${message}`);
});
socket.emit('sendMessage', 'Hello, WebSocket!');
Dzięki temu komunikacja między serwerem a klientem odbywa się w czasie rzeczywistym.
Taski asynchroniczne i harmonogramowanie
Harmonogramowanie zadań cyklicznych
Często w aplikacjach serwerowych musimy wykonywać pewne zadania cyklicznie, np. sprawdzanie stanu aplikacji, synchronizacja danych, wysyłanie powiadomień, itp. NestJS udostępnia narzędzie @nestjs/schedule, które pozwala na harmonogramowanie zadań.
Instalacja NestJS Schedule
Najpierw zainstalujmy zależności:
npm install @nestjs/schedule
npm install --save-dev @types/cron
Konfiguracja zadania cyklicznego
Po zainstalowaniu @nestjs/schedule możemy stworzyć zadanie cykliczne. Poniższy przykład pokazuje, jak skonfigurować zadanie wykonywane co minutę:
import { Injectable } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
@Injectable()
export class TasksService {
@Cron('*/1 * * * *')
handleCron() {
console.log('Running a task every minute');
}
}
@Cron('*/1 * * * *')– Używamy notacji CRON, która mówi, że zadanie ma być uruchamiane co minutę.
Aby nasz serwis działał, musimy zarejestrować ScheduleModule w module aplikacji (AppModule):
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
import { TasksService } from './tasks.service';
@Module({
imports: [ScheduleModule.forRoot()],
providers: [TasksService],
})
export class AppModule {}
Taski asynchroniczne
Często chcemy także, aby nasze taski działały asynchronicznie, co pozwala na wykonywanie bardziej skomplikowanych operacji, które nie blokują reszty aplikacji:
import { Injectable } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
@Injectable()
export class TasksService {
@Cron('*/5 * * * *')
async handleAsyncCron() {
console.log('Running an async task every 5 minutes...');
await new Promise((resolve) => setTimeout(resolve, 3000));
console.log('Finished async task');
}
}
W powyższym przykładzie zadanie jest wykonywane co 5 minut, a wewnątrz metody korzystamy z await, aby poczekać na zakończenie jakiejś asynchronicznej operacji.
Podsumowanie
W tym wpisie poznaliśmy trzy zaawansowane koncepcje w NestJS:
Interceptorzy – Narzędzie do logowania, modyfikacji odpowiedzi oraz modyfikowania przebiegu przetwarzania żądania i odpowiedzi. Dzięki interceptorom możemy śledzić czas wykonania operacji oraz dostosować odpowiedzi w jednolity sposób.
WebSockets i Eventy – Mechanizm do komunikacji w czasie rzeczywistym. Dzięki WebSocket Gateway możemy tworzyć aplikacje, które natychmiast reagują na zmiany, takie jak czaty czy transmisje na żywo.
Taski asynchroniczne i harmonogramowanie – Harmonogramowanie zadań za pomocą
@nestjs/schedule, co pozwala na uruchamianie okresowych procesów, takich jak synchronizacja danych, wysyłanie powiadomień czy inne rutynowe czynności.
Każda z tych koncepcji daje nam ogromne możliwości w zakresie rozbudowy funkcjonalności aplikacji. Dzięki zaawansowanym funkcjom NestJS możemy tworzyć aplikacje, które są nie tylko skalowalne, ale także wydajne, dynamiczne i bezpieczne. Zachęcam do eksperymentowania z przedstawionymi narzędziami, ponieważ ich opanowanie pozwala na znaczący rozwój jako programista NestJS.