How to consume gRPC in Node.js
Consuming gRPC services in Node.js enables high-performance communication with microservices using protocol buffers instead of JSON.
As the creator of CoreUI with over 10 years of Node.js experience since 2014, I’ve built numerous systems that consume gRPC services for real-time data processing and inter-service communication.
The standard approach uses the @grpc/grpc-js package with .proto files to define service contracts and generate client code.
This provides type-safe, efficient communication between services.
Install @grpc/grpc-js and @grpc/proto-loader to consume gRPC services.
npm install @grpc/grpc-js @grpc/proto-loader
These packages provide the core gRPC functionality for Node.js. The @grpc/grpc-js is the official pure JavaScript implementation. The @grpc/proto-loader loads .proto files and generates service definitions dynamically at runtime.
Creating a Proto File
Define the service contract in a .proto file.
// user.proto
syntax = "proto3";
package user;
service UserService {
rpc GetUser (UserRequest) returns (UserResponse);
rpc ListUsers (Empty) returns (UserListResponse);
}
message UserRequest {
string id = 1;
}
message UserResponse {
string id = 1;
string name = 2;
string email = 3;
}
message Empty {}
message UserListResponse {
repeated UserResponse users = 1;
}
This proto file defines a UserService with two RPC methods. The GetUser method takes a UserRequest with a user ID and returns a UserResponse. The ListUsers method returns all users. Protocol buffers provide strongly typed contracts between client and server.
Creating a gRPC Client
Load the proto file and create a client to call gRPC methods.
const grpc = require('@grpc/grpc-js')
const protoLoader = require('@grpc/proto-loader')
const PROTO_PATH = './user.proto'
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
})
const userProto = grpc.loadPackageDefinition(packageDefinition).user
const client = new userProto.UserService(
'localhost:50051',
grpc.credentials.createInsecure()
)
The protoLoader.loadSync() loads the proto file with options for handling data types. The grpc.loadPackageDefinition() converts it to service definitions. The client connects to the server at localhost:50051 using insecure credentials for development. For production, use SSL/TLS credentials.
Making Unary RPC Calls
Call a unary RPC method that sends one request and receives one response.
client.GetUser({ id: '123' }, (error, response) => {
if (error) {
console.error('Error:', error.message)
return
}
console.log('User:', response)
})
The GetUser method takes a request object with the user ID and a callback function. The callback receives an error (if any) and the response. This pattern is similar to traditional REST API calls but uses protocol buffers for serialization.
Using Promises with gRPC
Wrap gRPC calls in promises for async/await syntax.
const util = require('util')
const getUserAsync = util.promisify(client.GetUser.bind(client))
async function fetchUser(userId) {
try {
const user = await getUserAsync({ id: userId })
console.log('User:', user)
return user
} catch (error) {
console.error('Error fetching user:', error.message)
throw error
}
}
fetchUser('123')
The util.promisify() converts the callback-based gRPC method to a promise. The bind(client) ensures the method maintains the correct context. This allows using async/await for cleaner asynchronous code.
Handling Server Streaming
Consume server streaming RPCs that return multiple responses.
const call = client.ListUsers({})
call.on('data', (user) => {
console.log('Received user:', user)
})
call.on('end', () => {
console.log('Stream ended')
})
call.on('error', (error) => {
console.error('Stream error:', error.message)
})
Server streaming RPCs return a stream of responses. The data event fires for each response. The end event signals stream completion. The error event handles errors. This pattern is efficient for large datasets.
Adding Metadata and Deadlines
Include metadata headers and set request deadlines.
const metadata = new grpc.Metadata()
metadata.add('authorization', 'Bearer token123')
const deadline = new Date()
deadline.setSeconds(deadline.getSeconds() + 5)
client.GetUser(
{ id: '123' },
metadata,
{ deadline: deadline.getTime() },
(error, response) => {
if (error) {
console.error('Error:', error.message)
return
}
console.log('User:', response)
}
)
The grpc.Metadata object holds key-value headers sent with the request. The deadline specifies when the request should timeout. The metadata is passed as the second argument and options as the third. This provides control over authentication and request timing.
Best Practice Note
This is the same gRPC client pattern we use in CoreUI backend services for microservice communication and real-time data pipelines. Always use SSL/TLS credentials in production by replacing createInsecure() with createSsl(). Consider implementing retry logic and circuit breakers for resilient service communication. For complex systems with multiple gRPC services, centralize client creation and proto loading in a shared module. gRPC provides significant performance improvements over REST for high-throughput systems, especially when combined with proper connection pooling and keep-alive settings.



