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.

Architektura mikroserwisów NestJS z komunikacją gRPC między rozproszonymi serwisami backendowymi

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 12, natywnej obsłudze gRPC, filtrom wyjątków dla wywołań RPC oraz automatycznej propagacji trace ID framework dostarcza wszystko, czego potrzeba do rozbicia monolitu na dobrze odgraniczone serwisy bez utraty komfortu pracy programisty.

gRPC kontra REST w komunikacji wewnętrznej

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. NestJS 12 rozbudowuje to o hooki przed żądaniem, pozwalające handlerom wykonać własną logikę bezpośrednio przed wywołaniem dla zagadnień przekrojowych takich jak sprawdzanie autoryzacji czy metryki.

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.
orders.controller.tstypescript
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ą.

Deprecjacja EventPattern w NestJS 12

Począwszy od NestJS 12 transporty TCP, Redis, NATS, MQTT i RabbitMQ logują ostrzeżenie o deprecjacji, gdy handler @EventPattern odpowiada na ClientProxy#send(). To zachowanie zmieni się w następnej wersji głównej, zgodnie z istniejącym podejściem Kafki. Należy zmigrować handlery zdarzeń do prawdziwej semantyki fire-and-forget lub przełączyć się na @MessagePattern dla przepływów żądanie-odpowiedź.

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ć.

proto/users.protoprotobuf
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:

main.tstypescript
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:

users.controller.tstypescript
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: strumienie cen w czasie rzeczywistym, paginowane kursory bazy danych czy aktualizacje 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:

users.controller.ts (server streaming)typescript
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.

NestJS 12 dodaje obsługę backpressure: strumienie żądań wstrzymują się, gdy ich odpowiedzi czekają na zapis. Zapobiega to przepełnieniu bufora, gdy wolny konsument nie nadąża za szybkim producentem.

Obsługa błędów w strumieniowaniu

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.

Filtry wyjątków gRPC w NestJS 12

NestJS 12 wprowadza dedykowaną obsługę wyjątków dla gRPC, odzwierciedlając znany wzorzec filtrów wyjątków HTTP. Klasy wyjątków specyficzne dla statusu pozwalają handlerom zwracać natywne dla gRPC kody błędów zamiast generycznych awarii:

grpc-exception.filter.tstypescript
import { Catch, RpcExceptionFilter, ArgumentsHost } from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { RpcException } from '@nestjs/microservices';
import { status } from '@grpc/grpc-js';

@Catch(RpcException)
export class GrpcExceptionFilter implements RpcExceptionFilter<RpcException> {
  catch(exception: RpcException, host: ArgumentsHost): Observable<any> {
    const error = exception.getError() as { code?: number; message?: string };
    return throwError(() => ({
      code: error.code ?? status.INTERNAL,
      message: error.message ?? 'Internal error',
    }));
  }
}

Filtr można zastosować globalnie lub per kontroler, aby zapewnić spójne odpowiedzi błędów we wszystkich endpointach gRPC. W połączeniu z istniejącą klasą RpcException zapewnia to parytet obsługi błędów między transportami HTTP i 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:

main.ts (hybrid application)typescript
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.

Wzorce regexp dla Kafki w NestJS 12

NestJS 12 pozwala @MessagePattern() i @EventPattern() akceptować wyrażenia regularne dla tematów Kafki. Pojedynczy handler może teraz dynamicznie subskrybować wiele tematów:

multi-topic.controller.tstypescript
import { Controller } from '@nestjs/common';
import { EventPattern, Payload, Ctx, KafkaContext } from '@nestjs/microservices';

@Controller()
export class AnalyticsController {
  // Matches analytics.pageview, analytics.click, analytics.purchase, etc.
  @EventPattern(/^analytics..*/)
  handleAnalyticsEvent(
    @Payload() data: Record<string, unknown>,
    @Ctx() context: KafkaContext,
  ) {
    const topic = context.getTopic();
    console.log(`Received event on ${topic}`, data);
  }
}

Zmniejsza to boilerplate, gdy wiele tematów współdzieli tę samą logikę handlera. Walidacja klucza wzorca w NestJS 12 wymusza maksymalnie 256 znaków: dłuższe wzorce kończą się cichą porażką i nie pasują do żadnych handlerów.

Migracja NATS v3 w NestJS 12

NestJS 12 zastępuje klienta NATS oficjalnym pakietem @nats-io/transport-node. To zmiana łamiąca kompatybilność: deserializery otrzymują teraz pełny obiekt wiadomości zamiast surowego payloadu.

typescript
// Przed (NestJS 11)
deserialize(value: any): any {
  return JSON.parse(value);
}

// Po (NestJS 12): odczyt payloadu przez msg.json()
deserialize(msg: any): any {
  return msg.json();
}

Przy migracji do NestJS 12 należy zaktualizować wszystkie niestandardowe deserializery NATS. Nowy klient zapewnia lepszą wydajność i zgodność z oficjalnym ekosystemem NATS.

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 Order i OrderLine zawsze zmieniają się razem, należą do tego samego serwisu. Jeśli User i Order zmieniają 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.created przez Kafkę lub RabbitMQ. Serwis magazynowy reaguje, a serwis zamówień nie musi wiedzieć o jego istnieniu.
  • Współdzielone pakiety proto dla kontraktów. Pliki .proto należ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 12 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:

orders.service.tstypescript
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.

Propagacja trace ID w NestJS 12

NestJS 12 kontynuuje automatyczną propagację trace ID wprowadzoną w NestJS 11 przez transporty Kafka, RabbitMQ i gRPC. W połączeniu z OpenTelemetry umożliwia to pełne śledzenie rozproszone bez niestandardowych interceptorów.

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. NestJS 12 zdeprecjonował odpowiadanie z handlerów @EventPattern na większości transportów.

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.

Co zmieniło się dla mikroserwisów w NestJS 12?

NestJS 12 dodał filtry wyjątków gRPC dla obsługi błędów specyficznych dla statusu, wzorce regexp dla Kafki do obsługi wielu tematów, wsparcie NATS v3 z nowym pakietem klienta, hooki przed żądaniem dla handlerów mikroserwisów oraz ulepszone backpressure dla strumieni gRPC. Ostrzeżenie o deprecjacji @EventPattern sygnalizuje przyszłą zmianę zachowania, gdzie handlery zdarzeń nie będą już odpowiadać na wywołania send().

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. Przewodnik NestJS i GraphQL omawia alternatywę dla gRPC w typowanych API serwisów.

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Źródła

Kluczowe wnioski dotyczące mikroserwisów NestJS w 2026

  • Abstrakcja transportowa NestJS 12 pozwala serwisom przełączać się między TCP, gRPC, Kafką i NATS bez przepisywania logiki handlerów: dekoratory @MessagePattern i @EventPattern dział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, a NestJS 12 dodaje obsługę backpressure dla wolnych konsumentów
  • 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.

Wyzwanie dnia

Znajdziesz błąd w Node.js / NestJS?

Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Anthony Fillion-Maillet

Autor:

Anthony Fillion-Maillet

Założyciel SharpSkill

Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.

Zaktualizowano 22 października 2026

Tagi

#nestjs
#microservices
#grpc
#nodejs
#typescript
#architecture

Udostępnij

Powiązane artykuły