NestJS Enterprise Backend APIs · Lekcja

Definiowanie usług i komunikatów w Protobuf

Twórz kontrakty .proto i generuj typowane interfejsy dla mikrousług NestJS.

Lekcja 1 z 413 kroki

Definiowanie usług i komunikatów w Protobuf to bezpłatna lekcja NestJS Enterprise Backend APIs na CoddyKit. To lekcja 1 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej NestJS Enterprise Backend APIs, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs NestJS Enterprise Backend APIs zawiera 4 lekcji w sumie.

Części tej lekcji nie zostały jeszcze przetłumaczone i są wyświetlane po angielsku.

Why Protobuf Drives the Contract

In a NestJS gRPC microservice, the .proto file is the single source of truth. It defines the wire format, the RPC surface, and — once compiled — the TypeScript types both client and server share.

  • Messages describe the data shapes that travel over the wire.
  • Services declare the callable RPC methods and their request/response messages.

Unlike REST + OpenAPI (where the schema is often written after the code), with gRPC you author the contract first and generate code from it. This is contract-first design.

Anatomy of a .proto File

Every contract starts by declaring the syntax version and a package. The package becomes a namespace that NestJS uses to locate your service at runtime.

  • syntax = "proto3"; — always proto3 for modern gRPC.
  • package billing; — the namespace referenced in the NestJS transport options.

Below is a minimal but complete contract for an invoicing service.

// proto/billing.proto
syntax = "proto3";

package billing;

service InvoiceService {
  rpc GetInvoice (GetInvoiceRequest) returns (Invoice);
}

message GetInvoiceRequest {
  string id = 1;
}

message Invoice {
  string id = 1;
  string customer_id = 2;
  int64 amount_cents = 3;
  string currency = 4;
}

Field Numbers Are the Contract

Each field has a tag number (the = 1, = 2...). These numbers — not the field names — are what gets encoded on the wire.

  • Never reuse or renumber an existing field; doing so breaks binary compatibility with deployed clients.
  • Tags 1–15 use one byte; reserve them for the most frequently sent fields.
  • You may safely rename a field (the name is local to generated code), but never change its number or type.

When you remove a field, mark its number reserved so it is never accidentally recycled.

message Invoice {
  reserved 5, 6;
  reserved "legacy_tax_field";

  string id = 1;
  string customer_id = 2;
  int64 amount_cents = 3;
  string currency = 4;
}

Scalar Types and the int64 Trap

Proto3 scalars map to TypeScript, but the mapping has sharp edges for enterprise APIs handling money or IDs.

  • string → string, bool → boolean, int32/float/double → number.
  • int64, uint64, fixed64 → represented as string (or Long) by most loaders, because JS numbers lose precision beyond 2^53.

For amount_cents as int64, your generated interface should treat it as a string to avoid silent rounding on large values.

// Generated-style interface for the Invoice message
export interface Invoice {
  id: string;
  customerId: string;
  // int64 surfaces as string to preserve precision
  amountCents: string;
  currency: string;
}

snake_case In, camelCase Out

Protobuf convention is snake_case for field names. The NestJS gRPC loader (@grpc/proto-loader) defaults to keepCase: false, which converts fields to camelCase in the generated/runtime objects.

  • customer_id in .proto becomes customerId in TypeScript.
  • If you set keepCase: true, you must read customer_id verbatim — this is a frequent source of undefined bugs.

Pick one convention per project and configure the loader consistently.

// main.ts transport options
import { Transport, GrpcOptions } from '@nestjs/microservices';
import { join } from 'path';

export const grpcOptions: GrpcOptions = {
  transport: Transport.GRPC,
  options: {
    package: 'billing',
    protoPath: join(__dirname, 'proto/billing.proto'),
    loader: { keepCase: false, longs: String, enums: String },
  },
};

Enums and Their Zero-Value Rule

Proto3 enums must define a zero value as the first entry — it is the implicit default when a field is unset on the wire.

  • Name the zero value *_UNSPECIFIED so unset and "first real state" are distinguishable.
  • Enums are open in proto3: a client on a newer schema may send a number your server does not know, so always handle the default branch.

With enums: String in the loader, values arrive as their string names in TypeScript.

enum InvoiceStatus {
  INVOICE_STATUS_UNSPECIFIED = 0;
  INVOICE_STATUS_DRAFT = 1;
  INVOICE_STATUS_SENT = 2;
  INVOICE_STATUS_PAID = 3;
  INVOICE_STATUS_VOID = 4;
}

message Invoice {
  string id = 1;
  InvoiceStatus status = 5;
}

The Service Interface in NestJS

Each rpc method becomes a method on a generated TypeScript interface. Unary RPCs return an Observable (or Promise) of the response message in NestJS.

You typically hand-write or generate an interface and inject the client via ClientGrpc.getService().

import { Observable } from 'rxjs';

export interface GetInvoiceRequest {
  id: string;
}

export interface InvoiceServiceClient {
  getInvoice(request: GetInvoiceRequest): Observable<Invoice>;
}

export interface Invoice {
  id: string;
  customerId: string;
  amountCents: string;
  currency: string;
  status: string;
}

Implementing the Service Handler

On the server side, decorate a controller method with @GrpcMethod. The first argument is the service name from the .proto, the second is the rpc method name.

  • The method receives the decoded request message as a plain object.
  • Return the response message shape directly, or an Observable/Promise of it.
import { Controller } from '@nestjs/common';
import { GrpcMethod } from '@nestjs/microservices';

@Controller()
export class InvoiceController {
  @GrpcMethod('InvoiceService', 'GetInvoice')
  getInvoice(data: { id: string }): Invoice {
    return {
      id: data.id,
      customerId: 'cust_42',
      amountCents: '120000',
      currency: 'EUR',
      status: 'INVOICE_STATUS_SENT',
    };
  }
}

Nested Messages and Composition

Messages compose. A field can be another message type, letting you model rich aggregates without flattening everything into one shape.

  • Reference a message type by name; define it before or after — order does not matter within a file.
  • An unset nested message arrives as undefined in TypeScript, so guard before accessing its fields.
message Money {
  int64 amount_cents = 1;
  string currency = 2;
}

message LineItem {
  string sku = 1;
  uint32 quantity = 2;
  Money unit_price = 3;
}

message Invoice {
  string id = 1;
  string customer_id = 2;
  Money total = 3;
  repeated LineItem line_items = 4;
}

Repeated Fields, maps, and Streaming RPCs

Two more building blocks complete most enterprise contracts:

  • repeated T field = N; → a TypeScript array T[]. An empty list and an unset list are indistinguishable on the wire.
  • map<string, T> → a TypeScript record; useful for labels/metadata.

Prefix stream on the request, response, or both to declare server-streaming, client-streaming, or bidirectional RPCs. In NestJS, streaming methods use @GrpcStreamMethod and work with RxJS Observable streams.

service InvoiceService {
  rpc GetInvoice (GetInvoiceRequest) returns (Invoice);
  // server-streaming: many invoices for one query
  rpc ListInvoices (ListInvoicesRequest) returns (stream Invoice);
}

message ListInvoicesRequest {
  string customer_id = 1;
  map<string, string> filters = 2;
}

Generating Typed Interfaces with ts-proto

Hand-writing interfaces drifts from the .proto. Use a generator like ts-proto (via protoc) to emit interfaces, encode/decode helpers, and a NestJS-friendly client/service shape that stays in lockstep with the contract.

  • nestJs=true emits the gRPC service/client interfaces NestJS expects.
  • Run it in CI so a stale generated file fails the build, guaranteeing code matches the contract.
// package.json script
{
  "scripts": {
    "proto:gen": "protoc --plugin=node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=./src/generated --ts_proto_opt=nestJs=true,outputServices=grpc-js,useDate=false ./proto/billing.proto"
  }
}

Quick Check: Evolving the Contract

You shipped Invoice with string customer_id = 2;. A new requirement needs you to stop sending customer_id and instead send a richer Customer customer = 6; nested message. Deployed older clients must keep working.

Recap: Contract-First Protobuf

You can now author a service contract and turn it into typed NestJS interfaces:

  • Structure: syntax = proto3, a package namespace, message shapes, and a service with rpc methods.
  • Field numbers are the wire contract — additive evolution only; reserved retired numbers and names.
  • Type mapping: watch int64 → string, snake_case → camelCase, enums need a *_UNSPECIFIED zero value.
  • Composition: nested messages, repeated arrays, map, and stream RPCs.
  • Generation: drive types from the .proto with ts-proto in CI so code can never drift from the contract.
Bezpłatny start

Ucz się TypeScript dzięki korepetycjom AI — za darmo

Pisz i uruchamiaj kod w przeglądarce, otrzymuj natychmiastową pomoc od korepetytora AI dostępnego 24/7 i kontynuuj naukę w sieci lub w aplikacji.

Kursy
20
Lekcje
76

Często zadawane pytania

Czy lekcja „Definiowanie usług i komunikatów w Protobuf” jest bezpłatna?

Tak — pełny tekst „Definiowanie usług i komunikatów w Protobuf” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu NestJS Enterprise Backend APIs, przejdź na CoddyKit PRO. Kurs NestJS Enterprise Backend APIs zawiera 4 lekcji w sumie.

Co nauczysz się w „Definiowanie usług i komunikatów w Protobuf”?

Twórz kontrakty .proto i generuj typowane interfejsy dla mikrousług NestJS. Ćwiczysz NestJS Enterprise Backend APIs z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć NestJS Enterprise Backend APIs?

Nie wymagamy żadnego doświadczenia. NestJS Enterprise Backend APIs w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 1 z 4.

Ile czasu zajmuje lekcja „Definiowanie usług i komunikatów w Protobuf”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji NestJS Enterprise Backend APIs?

Tak. Każda lekcja NestJS Enterprise Backend APIs zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Definiowanie usług i komunikatów w Protobuf
  2. Implementowanie i wywoływanie metod gRPC
  3. Strumieniowanie RPC i kontrola przeciążenia
  4. Ewolucja kontraktów i zgodność wsteczna
← Powrót do NestJS Enterprise Backend APIs