Mikroserwisy w NestJS w 2026: architektura, gRPC i pytania rekrutacyjne
Praktyczny przewodnik po architekturze mikroserwisów NestJS z gRPC: granice serwisów, warstwy transportowe, wzorce strumieniowania oraz najczęstsze pytania rekrutacyjne na 2026 rok.

Mikroserwisy NestJS stały się standardowym podejściem do budowy rozproszonych backendów Node.js w 2026 roku. Dzięki ulepszonej warstwie transporterów w NestJS 11, natywnej obsłudze gRPC oraz automatycznej propagacji trace ID framework dostarcza wszystko, czego potrzeba do rozbicia monolitu na dobrze odgraniczone serwisy bez utraty komfortu pracy programisty.
gRPC korzysta z Protocol Buffers oraz HTTP/2, oferując nawet 10-krotnie szybszą serializację i natywne strumieniowanie dwukierunkowe w porównaniu z JSON przez REST. W komunikacji serwis-serwis wewnątrz klastra gRPC zmniejsza opóźnienia i wymusza ścisłe kontrakty poprzez pliki .proto. REST pozostaje lepszym wyborem dla publicznych API obsługiwanych przez przeglądarki i podmioty zewnętrzne.
Architektura warstwy transportowej mikroserwisów NestJS
Pakiet @nestjs/microservices zastępuje kontrolery HTTP handlerami zorientowanymi na wiadomości. Zamiast routingu po ścieżce URL każdy handler reaguje na wzorzec wiadomości (message pattern) — ciąg znaków lub klucz obiektu, który identyfikuje operację. Ta abstrakcja sprawia, że ta sama logika handlera działa na TCP, Redisie, NATS, Kafce, RabbitMQ czy gRPC bez zmian w kodzie.
NestJS 11 wprowadził metodę unwrap() na wszystkich transporterach, dającą bezpośredni dostęp do bazowej instancji klienta. Rozwiązuje to długotrwały problem: inspekcję stanu połączenia, strojenie interwałów keepalive czy dostęp do funkcji specyficznych dla brokera, których abstrakcja NestJS nie udostępnia.
Dwa wzorce komunikacji napędzają mikroserwisy NestJS:
- Żądanie-odpowiedź (
@MessagePattern): klient wysyła wiadomość i czeka na odpowiedź. Odpowiednie dla zapytań synchronicznych, takich jak pobranie profilu użytkownika czy weryfikacja tokenu. - Zdarzeniowy (
@EventPattern): klient emituje zdarzenie i kontynuuje pracę — nie oczekuje odpowiedzi. Idealne dla logów audytowych, powiadomień czy wyzwalania procesów po stronie odbiorcy.
import { Controller } from '@nestjs/common';
import { MessagePattern, EventPattern, Payload } from '@nestjs/microservices';
import { OrdersService } from './orders.service';
import { CreateOrderDto } from './dto/create-order.dto';
@Controller()
export class OrdersController {
constructor(private readonly ordersService: OrdersService) {}
// Request-response: caller waits for the created order
@MessagePattern('order.create')
async createOrder(@Payload() data: CreateOrderDto) {
return this.ordersService.create(data);
}
// Event-based: fire and forget, no response returned
@EventPattern('order.shipped')
async handleOrderShipped(@Payload() data: { orderId: string }) {
await this.ordersService.markAsShipped(data.orderId);
}
}Handler @MessagePattern automatycznie serializuje wartość zwracaną i odsyła ją przez transport. Handler @EventPattern nie zwraca niczego — NestJS odrzuca każdą wartość zwracaną.
Konfiguracja gRPC jako transportu mikroserwisu NestJS
Integracja gRPC w NestJS zaczyna się od pliku .proto, który definiuje kontrakt serwisu. Protocol Buffers wymuszają bezpieczeństwo typów na poziomie sieci — zarówno klient, jak i serwer muszą zgodzić się na dokładny kształt każdej wiadomości, zanim choćby jeden bajt przejdzie przez sieć.
syntax = "proto3";
package users;
service UsersService {
rpc FindOne (UserById) returns (User);
rpc FindMany (UserFilter) returns (stream User);
}
message UserById {
string id = 1;
}
message UserFilter {
string role = 1;
int32 limit = 2;
}
message User {
string id = 1;
string email = 2;
string name = 3;
string role = 4;
}Po stronie serwera aplikacja NestJS uruchamia mikroserwis gRPC, wskazując plik proto i wiążąc adres sieciowy:
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { join } from 'path';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.GRPC,
options: {
package: 'users',
protoPath: join(__dirname, 'proto/users.proto'),
url: '0.0.0.0:5000',
},
},
);
await app.listen();
}
bootstrap();Kontroler używa @GrpcMethod zamiast @MessagePattern. Dekorator przyjmuje nazwę serwisu i nazwę metody RPC jako argumenty, mapując je bezpośrednio na definicję proto:
import { Controller } from '@nestjs/common';
import { GrpcMethod } from '@nestjs/microservices';
import { UsersService } from './users.service';
@Controller()
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@GrpcMethod('UsersService', 'FindOne')
async findOne(data: { id: string }) {
return this.usersService.findById(data.id);
}
}NestJS automatycznie obsługuje ładowanie proto, serializację i zarządzanie połączeniem HTTP/2. Metoda kontrolera otrzymuje zwykłe obiekty TypeScript — bez ręcznego dekodowania proto.
Wzorce strumieniowania gRPC w NestJS
Strumieniowanie po stronie serwera pasuje do scenariuszy, w których pojedyncze żądanie generuje wiele wyników w czasie — pomyśl o strumieniach cen w czasie rzeczywistym, paginowanych kursorach bazy danych czy aktualizacjach postępu podczas długo trwającego zadania. Handler zwraca Observable z RxJS, a NestJS strumieniuje każdą wyemitowaną wartość do klienta jako osobną wiadomość gRPC:
import { Observable, from } from 'rxjs';
import { map } from 'rxjs/operators';
import { GrpcMethod } from '@nestjs/microservices';
@GrpcMethod('UsersService', 'FindMany')
findMany(data: { role: string; limit: number }): Observable<any> {
// Stream users matching the filter one by one
const users$ = from(this.usersService.findByRole(data.role, data.limit));
return users$.pipe(
map((user) => ({
id: user.id,
email: user.email,
name: user.name,
role: user.role,
})),
);
}Strumieniowanie dwukierunkowe korzysta z @GrpcStreamCall(). Zarówno klient, jak i serwer wysyłają niezależne strumienie — przydatne w systemach czatu, edycji współbieżnej czy zwielokrotnionym pozyskiwaniu telemetrii. Handler otrzymuje instancję grpc.ServerDuplexStream i samodzielnie zarządza cyklem odczytu/zapisu.
Obserwator (Observable), który ulegnie awarii wewnątrz strumienia gRPC, kończy całe wywołanie ze statusem CANCELLED. Logikę strumienia należy opakować w operatory catchError i emitować odpowiednie dla gRPC metadane błędu. Nieobsłużone błędy strumienia są przyczyną numer jeden tajemniczych zerwań połączeń w produkcyjnych serwisach NestJS gRPC.
Aplikacje hybrydowe: HTTP i gRPC w jednym serwisie
Wiele rzeczywistych serwisów NestJS potrzebuje zarówno publicznego REST API, jak i wewnętrznych endpointów gRPC. Wzorzec aplikacji hybrydowej łączy wiele transportów z jedną instancją NestJS:
import { NestFactory } from '@nestjs/core';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import { join } from 'path';
import { AppModule } from './app.module';
async function bootstrap() {
// HTTP server on port 3000
const app = await NestFactory.create(AppModule);
// gRPC microservice on port 5000
app.connectMicroservice<MicroserviceOptions>({
transport: Transport.GRPC,
options: {
package: 'users',
protoPath: join(__dirname, 'proto/users.proto'),
url: '0.0.0.0:5000',
},
});
await app.startAllMicroservices();
await app.listen(3000);
}
bootstrap();To podejście pozwala uniknąć wdrażania osobnych serwisów, gdy pojedynczy odgraniczony kontekst potrzebuje zarówno interfejsu zewnętrznego, jak i wewnętrznego. Ten sam kontener wstrzykiwania zależności, strażnicy, interceptory i pipe'y obowiązują dla obu transportów — co ogranicza duplikację i zachowuje spójność zachowania.
Gotowy na rozmowy o Node.js / NestJS?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
Granice serwisów i Domain-Driven Design w NestJS
Rozbicie monolitu na mikroserwisy bez jasnych granic tworzy rozproszony monolit — najgorsze cechy obu architektur. Każdy mikroserwis NestJS powinien posiadać pojedynczy odgraniczony kontekst: własną bazę danych, własne modele domenowe i własny cykl życia wdrożenia.
Praktyczne wskazówki dotyczące wyznaczania granic serwisów w NestJS:
- Jeden moduł NestJS na korzeń agregatu. Jeśli
OrderiOrderLinezawsze zmieniają się razem, należą do tego samego serwisu. JeśliUseriOrderzmieniają się niezależnie, zasługują na osobne serwisy. - Zdarzenia asynchroniczne dla efektów ubocznych między domenami. Gdy zostaje złożone zamówienie, należy wyemitować zdarzenie
order.createdprzez Kafkę lub RabbitMQ. Serwis magazynowy reaguje, a serwis zamówień nie musi wiedzieć o jego istnieniu. - Współdzielone pakiety proto dla kontraktów. Pliki
.protonależy przechowywać we wspólnym repozytorium. Zarówno producent, jak i konsument generują typy z tego samego źródła prawdy — rozjazd staje się błędem CI, a nie incydentem produkcyjnym.
Opcje mikroserwisów z kontenera DI w NestJS 11 sprawiają, że konfiguracja serwisu jest testowalna. Zamiast wpisywać na sztywno adresy brokera w main.ts, można wstrzyknąć ConfigService i podmieniać ciągi połączeń per środowisko bez przebudowy.
Wzorce niezawodności: timeouty, ponowienia i bezpieczniki
Wewnętrzne wywołania gRPC bez terminów (deadline) zamieniają pojedynczą wolną zależność w awarię całego systemu. Każde wywołanie ClientProxy.send() powinno zawierać timeout:
import { Inject, Injectable } from '@nestjs/common';
import { ClientGrpc } from '@nestjs/microservices';
import { firstValueFrom, timeout, retry } from 'rxjs';
@Injectable()
export class OrdersService {
private usersService: any;
constructor(@Inject('USERS_PACKAGE') private client: ClientGrpc) {}
onModuleInit() {
this.usersService = this.client.getService('UsersService');
}
async getOrderWithUser(orderId: string, userId: string) {
// 3-second deadline, 2 retries with exponential backoff
const user = await firstValueFrom(
this.usersService.findOne({ id: userId }).pipe(
timeout(3000),
retry({ count: 2, delay: (err, retryCount) => {
const jitter = Math.random() * 100;
return new Promise(r => setTimeout(r, 1000 * retryCount + jitter));
}}),
),
);
return { orderId, user };
}
}Ponowienia powinny dotyczyć wyłącznie operacji idempotentnych. Ponowienie wywołania CreateOrder grozi powstaniem duplikatów zamówień. Ponowienie zapytania FindOne jest bezpieczne, ponieważ nie wywołuje efektów ubocznych.
W zakresie bezpieczników (circuit breaker) biblioteka opossum dobrze integruje się z NestJS. Wywołania ClientGrpc należy opakować w bezpiecznik, który otwiera się po N kolejnych awariach i zamyka po konfigurowalnym czasie resetu.
NestJS 11 automatycznie propaguje trace ID przez transporty Kafka, RabbitMQ i gRPC. W połączeniu z OpenTelemetry umożliwia to pełne śledzenie rozproszone bez niestandardowych interceptorów — znacząca poprawa względem NestJS 10, gdzie wymagana była ręczna propagacja kontekstu.
Pytania rekrutacyjne o mikroserwisy NestJS
Rozmowy techniczne na stanowiska backendowe coraz częściej sprawdzają wiedzę o mikroserwisach obok wzorców specyficznych dla frameworka. Poniższe pytania obejmują koncepcje najczęściej poruszane na rozmowach skupionych na NestJS.
Jaka jest różnica między @MessagePattern a @EventPattern?
@MessagePattern realizuje żądanie-odpowiedź: klient wysyła wiadomość i blokuje się, aż otrzyma odpowiedź. @EventPattern realizuje model fire-and-forget: klient emituje zdarzenie i natychmiast kontynuuje wykonanie. Pod spodem @MessagePattern zwraca wynik handlera przez transport, podczas gdy @EventPattern odrzuca wartości zwracane.
Jak NestJS ładuje i wiąże pliki .proto dla gRPC?
NestJS używa pakietu @grpc/proto-loader do parsowania plików .proto przy starcie i generowania deskryptorów serwisów. Konfiguracja GrpcOptions określa ścieżkę proto i nazwę pakietu. Kontrolery oznaczone @GrpcMethod('ServiceName', 'MethodName') są dopasowywane do sparsowanych deskryptorów. Jeśli metoda zadeklarowana w proto nie ma pasującego handlera, NestJS zgłasza błąd przy bootstrapie.
Kiedy gRPC powinno zastąpić REST między mikroserwisami?
gRPC pasuje do wewnętrznych wywołań serwis-serwis, gdzie liczą się bezpieczeństwo typów, wydajność i strumieniowanie. Protocol Buffers tworzą mniejsze ładunki i szybszą serializację niż JSON. Multipleksowanie HTTP/2 zmniejsza narzut połączeń. REST pozostaje preferowany dla publicznych API, klientów przeglądarkowych i integracji zewnętrznych, gdzie czytelność dla człowieka i szerokie wsparcie narzędzi przeważają nad surową wydajnością.
Jak działa wzorzec aplikacji hybrydowej?
NestFactory.create() uruchamia serwer HTTP. Wywołanie app.connectMicroservice() dołącza dodatkowe transporty — gRPC, Kafkę, Redisa lub dowolny wspierany transporter — do tej samej instancji aplikacji NestJS. Wszystkie transporty dzielą to samo drzewo modułów, kontener DI i potok middleware. app.startAllMicroservices() uruchamia wszystkie podłączone transportery, a app.listen() uruchamia warstwę HTTP.
Jakie wzorce niezawodności zapobiegają kaskadowym awariom?
Najważniejsze są trzy wzorce: timeouty (każde wywołanie RPC potrzebuje terminu), ponowienia z jitterem (tylko dla operacji idempotentnych, z wykładniczym backoffem plus losowym jitterem, aby uniknąć efektu stada), oraz bezpieczniki (otwarte po N awariach, półotwarte po okresie schłodzenia, zamknięte po pomyślnych testach zdrowotności). Integracja NestJS z RxJS sprawia, że timeouty i ponowienia można komponować przez operatory pipe.
Dla głębszej praktyki we wzorcach architektury NestJS moduł moduły i wstrzykiwanie zależności w NestJS omawia podstawy DI, które stanowią fundament kompozycji mikroserwisów. Moduł middleware i interceptory zajmuje się zagadnieniami przekrojowymi współdzielonymi między transportami.
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Podsumowanie
- Abstrakcja transportowa NestJS 11 pozwala serwisom przełączać się między TCP, gRPC, Kafką i NATS bez przepisywania logiki handlerów — dekoratory
@MessagePatterni@EventPatterndziałają identycznie we wszystkich transportach - gRPC z Protocol Buffers wymusza ścisłe kontrakty między serwisami poprzez pliki
.proto, wychwytując błędy integracyjne na etapie kompilacji, a nie w produkcji - Aplikacje hybrydowe łączą HTTP i gRPC w jednej instancji NestJS, eliminując potrzebę wdrażania osobnych serwisów dla interfejsów publicznych i wewnętrznych
- Strumieniowanie po stronie serwera przez Observable z RxJS umożliwia strumienie danych w czasie rzeczywistym bez złożoności WebSocketów — NestJS mapuje każdą emisję Observable na wiadomość strumienia gRPC
- Każde wewnętrzne wywołanie RPC wymaga terminu, ponowienia dotyczą wyłącznie operacji idempotentnych, a bezpieczniki zapobiegają temu, by pojedynczy wolny serwis kaskadowo wywołał pełną awarię
- Granice serwisów wynikają z Domain-Driven Design: jeden odgraniczony kontekst na serwis, zdarzenia asynchroniczne dla komunikacji między domenami, współdzielone pakiety proto dla egzekwowania kontraktów
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Tagi
Udostępnij
Powiązane artykuły

NestJS + Prisma: nowoczesny stack backendowy dla Node.js
Kompletny przewodnik po budowaniu nowoczesnego API backendowego z NestJS i Prismą. Konfiguracja, modele, serwisy, transakcje i dobre praktyki.

NestJS: Budowanie kompletnego REST API
Kompletny przewodnik po budowaniu profesjonalnego REST API z NestJS. Kontrolery, serwisy, moduły, walidacja z class-validator i obsługa błędów z praktycznymi przykładami.

Rozmowa NestJS: Guards, Interceptory i architektura modułowa
Częste pytania na rozmowach technicznych z NestJS dotyczące Guards, Interceptorów i architektury modułowej, z konkretnymi przykładami kodu w TypeScript i wyjaśnieniami technicznymi.