Microservices die razendsnel met elkaar praten, bidirectionele streaming en strikt gedefinieerde contracten: dat is wat gRPC services in Node.js je opleveren. Waar REST vaak goed genoeg is, loop je bij high-throughput interne communicatie snel tegen de grenzen van JSON en HTTP/1.1 aan.
In deze handleiding bouw je stap voor stap een productieklare gRPC service met Node.js. Je leert hoe je .proto bestanden ontwerpt, unary en streaming calls implementeert, authenticatie toevoegt en je service klaarmaakt voor productie.
Waarom gRPC gebruiken?
gRPC is een open-source RPC-framework van Google dat gebruikmaakt van HTTP/2 en Protocol Buffers. Het resultaat: binaire payloads die 3 tot 10 keer kleiner zijn dan JSON, multiplexed verbindingen en automatisch gegenereerde clients in meer dan tien talen.
Voor interne microservices is dat een grote winst. Waar je bij REST handmatig schemas bijhoudt (zie onze REST API design best practices), krijg je bij gRPC een strikt contract dat zowel client als server dwingt om consistent te blijven.
Typische use cases:
- Service-to-service communicatie in een microservices architectuur
- Realtime dashboards met server streaming
- Chat- en synchronisatiediensten met bidirectionele streaming
- Performance-kritische interne APIs
Stap 1: Project opzetten
Start met een lege Node.js project en installeer de benodigde packages. We gebruiken @grpc/grpc-js, de pure JavaScript implementatie, en @grpc/proto-loader om .proto bestanden dynamisch te laden.
mkdir grpc-demo && cd grpc-demo
npm init -y
npm install @grpc/grpc-js @grpc/proto-loader
npm install -D typescript @types/node tsx
Voor TypeScript-setup in Node.js verwijs ik naar onze TypeScript in Node.js projecten gids. De voorbeelden hieronder werken zowel in JavaScript als TypeScript.
Maak de projectstructuur aan:
grpc-demo/
├── proto/
│ └── user.proto
├── src/
│ ├── server.ts
│ └── client.ts
└── package.json
Stap 2: Je eerste .proto bestand schrijven
Protocol Buffers definiëren het contract tussen client en server. Maak proto/user.proto:
syntax = "proto3";
package user;
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc ListUsers (ListUsersRequest) returns (stream User);
rpc CreateUser (CreateUserRequest) returns (User);
rpc ChatStream (stream ChatMessage) returns (stream ChatMessage);
}
message GetUserRequest {
string id = 1;
}
message User {
string id = 1;
string name = 2;
string email = 3;
int64 created_at = 4;
}
message ListUsersRequest {
int32 limit = 1;
string cursor = 2;
}
message CreateUserRequest {
string name = 1;
string email = 2;
}
message ChatMessage {
string user_id = 1;
string text = 2;
int64 timestamp = 3;
}
Merk op hoe je vier RPC-types definieert: unary (GetUser), server streaming (ListUsers), client unary met response (CreateUser) en bidirectionele streaming (ChatStream). De nummers achter velden zijn de tag numbers en moeten stabiel blijven voor backwards compatibility.
Stap 3: De gRPC server bouwen
Nu implementeer je de server. Laad het .proto bestand en koppel handlers aan elke RPC-methode.
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import path from 'path';
const PROTO_PATH = path.join(__dirname, '../proto/user.proto');
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const proto: any = grpc.loadPackageDefinition(packageDefinition).user;
const users = new Map<string, any>([
['1', { id: '1', name: 'Sander', email: '[email protected]', created_at: Date.now() }],
]);
const handlers = {
GetUser: (call: any, callback: any) => {
const user = users.get(call.request.id);
if (!user) {
return callback({
code: grpc.status.NOT_FOUND,
message: `User ${call.request.id} niet gevonden`,
});
}
callback(null, user);
},
ListUsers: (call: any) => {
for (const user of users.values()) {
call.write(user);
}
call.end();
},
CreateUser: (call: any, callback: any) => {
const { name, email } = call.request;
if (!name || !email) {
return callback({
code: grpc.status.INVALID_ARGUMENT,
message: 'name en email zijn verplicht',
});
}
const id = String(users.size + 1);
const user = { id, name, email, created_at: Date.now() };
users.set(id, user);
callback(null, user);
},
ChatStream: (call: any) => {
call.on('data', (msg: any) => {
call.write({
user_id: 'server',
text: `Echo: ${msg.text}`,
timestamp: Date.now(),
});
});
call.on('end', () => call.end());
},
};
const server = new grpc.Server();
server.addService(proto.UserService.service, handlers);
server.bindAsync(
'0.0.0.0:50051',
grpc.ServerCredentials.createInsecure(),
(err, port) => {
if (err) throw err;
console.log(`gRPC server draait op poort ${port}`);
}
);
Let op hoe streaming methodes geen callback gebruiken maar het call object met write() en end(). Voor client streaming zou je juist call.on('data') gebruiken om inkomende berichten te verwerken.
Stap 4: De client implementeren
De client laadt hetzelfde .proto bestand en krijgt automatisch typed methodes.
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import path from 'path';
const packageDefinition = protoLoader.loadSync(
path.join(__dirname, '../proto/user.proto'),
{ keepCase: true, longs: String, enums: String, defaults: true, oneofs: true }
);
const proto: any = grpc.loadPackageDefinition(packageDefinition).user;
const client = new proto.UserService(
'localhost:50051',
grpc.credentials.createInsecure()
);
client.GetUser({ id: '1' }, (err: any, user: any) => {
if (err) return console.error('Error:', err.message);
console.log('User:', user);
});
const stream = client.ListUsers({ limit: 100 });
stream.on('data', (user: any) => console.log('Received:', user));
stream.on('end', () => console.log('Stream klaar'));
stream.on('error', (err: any) => console.error(err));
Voor een betere developer experience kun je callbacks omzetten naar promises. Zie onze async programming in Node.js gids voor patterns met util.promisify.
Stap 5: Streaming in de diepte
Server streaming is de eenvoudigste streaming-vorm: je stuurt één request, de server stuurt meerdere responses terug. Dit past perfect bij paginated data, live updates of log tailing.
Bij bidirectionele streaming praten client en server gelijktijdig over dezelfde verbinding. Handig voor chat, games of realtime sync. Net als bij WebSockets geldt: let op backpressure. Lees onze streaming en backpressure in Node.js uitleg voor achtergrond.
const chat = client.ChatStream();
chat.on('data', (msg: any) => console.log(`${msg.user_id}: ${msg.text}`));
chat.write({ user_id: 'sander', text: 'Hallo server!', timestamp: Date.now() });
Bij grote datasets is het verstandig om call.write() te combineren met drain events, zodat je geen geheugenproblemen krijgt als de consumer trager is dan de producer.
Stap 6: Authenticatie en metadata
gRPC gebruikt metadata (vergelijkbaar met HTTP headers) voor authenticatie. Voor productie zet je TLS aan en voeg je een interceptor toe die tokens valideert.
const authInterceptor = (call: any, callback: any, next: any) => {
const token = call.metadata.get('authorization')[0];
if (!token || !verifyJwt(token as string)) {
return callback({
code: grpc.status.UNAUTHENTICATED,
message: 'Ongeldige of ontbrekende token',
});
}
next(call, callback);
};
Client-side stuur je metadata mee:
const metadata = new grpc.Metadata();
metadata.add('authorization', `Bearer ${token}`);
client.GetUser({ id: '1' }, metadata, (err, user) => { /* ... */ });
Voor token-strategieën, zie onze authentication in Node.js gids. JWT past goed bij gRPC omdat je tokens stateless verifieert per call.
Stap 7: Error handling en deadlines
Elke RPC-call krijgt een deadline. Zonder deadline loopt een traag of hangend verzoek oneindig door. Zet altijd een deadline aan de client-kant:
const deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + 5);
client.GetUser({ id: '1' }, { deadline }, (err, user) => { /* ... */ });
Aan de server-kant check je call.cancelled om werk af te breken als de client is weggelopen. Combineer dit met de strategieën uit onze error handling op schaal post voor retries, circuit breakers en observability.
gRPC kent een vaste set status codes zoals DEADLINE_EXCEEDED, UNAVAILABLE en RESOURCE_EXHAUSTED. Map je domeinfouten consistent op deze codes.
Stap 8: Productie checklist
Voor je naar productie gaat, loop deze punten langs:
- TLS aanzetten met
grpc.ServerCredentials.createSsl()en echte certificaten - Health checks toevoegen via het standaard grpc-health-probe protocol
- Observability: integreer OpenTelemetry voor tracing en metrics per RPC
- Connection keepalive instellen om dode connecties te detecteren
- Resource limits via
grpc.max_receive_message_lengthom DoS te voorkomen - Graceful shutdown met
server.tryShutdown()zodat actieve calls kunnen afronden
Combineer dit met de bredere production readiness checklist voor Node.js applicaties.
Wanneer kies je gRPC niet?
gRPC is krachtig, maar niet altijd de beste keuze. Voor publieke APIs die door browsers of derde partijen worden geconsumeerd, blijft REST of GraphQL laagdrempeliger. Debuggen is ook lastiger: je kunt niet zomaar curl gebruiken, al helpen tools als grpcurl en Postman's gRPC-ondersteuning.
Voor hoge throughput interne communicatie tussen microservices is gRPC moeilijk te verslaan. Voor event-driven architecturen met async messaging blijft Kafka een betere keuze, zie onze Kafka integratie in Node.js gids.
Veelgestelde vragen
Wat is het verschil tussen gRPC en REST?
gRPC gebruikt HTTP/2 en Protocol Buffers voor efficiënte binaire communicatie, terwijl REST werkt met HTTP/1.1 en JSON. gRPC is sneller en ondersteunt bidirectionele streaming, maar REST is laagdrempeliger voor browsers en publieke APIs.
Is gRPC sneller dan REST in Node.js?
Ja, in de meeste gevallen wel. Protocol Buffers zijn compacter dan JSON en HTTP/2 multiplexing verlaagt latency. Voor service-to-service communicatie in microservices haal je vaak 2 tot 5 keer hogere throughput.
Kan ik gRPC gebruiken vanuit de browser?
Niet direct. Browsers ondersteunen geen ruwe gRPC. Je hebt gRPC-Web nodig met een proxy zoals Envoy die tussen browser en gRPC-server zit. Voor interne services tussen Node.js is dat geen probleem.
Welk pakket gebruik ik voor gRPC in Node.js?
Gebruik @grpc/grpc-js, de pure JavaScript implementatie die actief wordt onderhouden door het gRPC-team. Combineer met @grpc/proto-loader om .proto bestanden dynamisch in te laden.
Hoe handel ik errors af in gRPC?
gRPC gebruikt status codes zoals NOT_FOUND, INVALID_ARGUMENT en UNAUTHENTICATED. Gooi errors met een code en message vanuit je handler, en vang ze client-side via de error parameter van de callback of try/catch bij promises.
Verder lezen
Wil je dieper in het ecosysteem duiken? De officiële gRPC documentatie is uitgebreid en praktisch. Voor geavanceerde patterns als load balancing, retries en xDS-configuratie is de grpc-node GitHub repository een goede bron.
Binnen onze serie sluit gRPC goed aan op onderwerpen als clustering en scaling en performance tuning in Node.js. Zo bouw je aan services die niet alleen snel zijn, maar ook betrouwbaar schalen.