Your first gRPC API in Node.js
Source: Dev.to

Why use gRPC?
gRPC is a high‑performance, open‑source universal RPC framework that runs natively on microservices and serverless architectures.
Example
Rest API
A simple example of a REST API for a restaurant service that returns a list of restaurants based on restaurant IDs.
// restaurant-service
import express from "express";
const app = express();
app.use(express.json());
const restaurantsData = [
{
id: "res_001",
name: "Spicy Garden",
cuisine: "Indian",
location: "New York",
rating: 4.5,
isOpen: true,
},
{
id: "res_002",
name: "Sushi Zen",
cuisine: "Japanese",
location: "San Francisco",
rating: 4.7,
isOpen: false,
},
{
id: "res_003",
name: "Pasta Palace",
cuisine: "Italian",
location: "Chicago",
rating: 4.2,
isOpen: true,
},
{
id: "res_004",
name: "Burger Hub",
cuisine: "American",
location: "Austin",
rating: 4.0,
isOpen: true,
},
{
id: "res_005",
name: "Dragon Wok",
cuisine: "Chinese",
location: "Seattle",
rating: 4.6,
isOpen: false,
},
];
app.post("/restaurants/bulk", (req, res) => {
const { restaurantIds } = req.body;
const result = restaurantsData.filter((r) =>
restaurantIds.includes(r.id)
);
res.json(result);
});
app.listen(3001, () => {
console.log("Server is running on port 3001");
});
Convert this REST API to gRPC
1. Define a .proto file
The .proto file is a language‑neutral, platform‑neutral, and protocol‑neutral Interface Definition Language (IDL) that describes the service and message formats.
See the docs for more details:
// restaurants.proto
syntax = "proto3";
package restaurants;
service RestaurantService {
rpc GetRestaurantsBulk (BulkRestaurantRequest) returns (BulkRestaurantResponse);
}
message BulkRestaurantRequest {
repeated string restaurantIds = 1;
}
message Restaurant {
string id = 1;
string name = 2;
string cuisine = 3;
string location = 4;
double rating = 5;
bool isOpen = 6;
}
message BulkRestaurantResponse {
repeated Restaurant restaurants = 1;
}
2. Implement a gRPC server that replaces the Express route
Install dependencies
bun add @grpc/grpc-js @grpc/proto-loader
Server code (grpc-server.js)
// restaurant-service/grpc-server.js
import grpc from "@grpc/grpc-js";
import protoLoader from "@grpc/proto-loader";
import path from "path";
const PROTO_PATH = path.resolve("restaurants.proto");
// Load proto definition
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const restaurantProto = grpc.loadPackageDefinition(packageDefinition).restaurants;
// Mock data (same as Express)
const restaurantsData = [
{
id: "res_001",
name: "Spicy Garden",
cuisine: "Indian",
location: "New York",
rating: 4.5,
isOpen: true,
},
{
id: "res_002",
name: "Sushi Zen",
cuisine: "Japanese",
location: "San Francisco",
rating: 4.7,
isOpen: false,
},
{
id: "res_003",
name: "Pasta Palace",
cuisine: "Italian",
location: "Chicago",
rating: 4.2,
isOpen: true,
},
{
id: "res_004",
name: "Burger Hub",
cuisine: "American",
location: "Austin",
rating: 4.0,
isOpen: true,
},
{
id: "res_005",
name: "Dragon Wok",
cuisine: "Chinese",
location: "Seattle",
rating: 4.6,
isOpen: false,
},
];
// gRPC method implementation
function getRestaurantsBulk(call, callback) {
const { restaurantIds } = call.request;
const restaurants = restaurantsData.filter((r) =>
restaurantIds.includes(r.id)
);
callback(null, { restaurants });
}
// Create and start the server
const server = new grpc.Server();
server.addService(restaurantProto.RestaurantService.service, {
GetRestaurantsBulk: getRestaurantsBulk,
});
server.bindAsync(
"0.0.0.0:50051",
grpc.ServerCredentials.createInsecure(),
(port, error) => {
if (error) {
console.error("Error binding server:", error);
return;
}
console.log("gRPC Server running on port", port);
server.start();
}
);
3. Create a gRPC client that replaces the Express client
// user-service/grpc-client.ts
import express from "express";
import grpc from "@grpc/grpc-js";
import protoLoader from "@grpc/proto-loader";
import path from "path";
const app = express();
// Load proto definition
const PROTO_PATH = path.resolve("restaurants.proto");
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const restaurantProto: any = grpc.loadPackageDefinition(packageDefinition).restaurants;
// Create a client that talks to the gRPC server
const restaurantClient = new restaurantProto.RestaurantService(
"localhost:50051",
grpc.credentials.createInsecure()
);
// Example endpoint that uses the gRPC client
app.get("/my-visited-restaurants", (req, res) => {
// In a real scenario, you would get these IDs from the user's history
const visitedIds = ["res_001", "res_003", "res_005"];
restaurantClient.GetRestaurantsBulk(
{ restaurantIds: visitedIds },
(err: any, response: any) => {
if (err) {
console.error("gRPC error:", err);
return res.status(500).send("Internal Server Error");
}
res.json(response.restaurants);
}
);
});
app.listen(3002, () => {
console.log("User service listening on port 3002");
});
Additional client example
import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import express from 'express';
const app = express();
const restaurantProtoPath = __dirname + '/proto/restaurant.proto';
const restaurantPackageDefinition = protoLoader.loadSync(restaurantProtoPath, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const restaurantProto: any = grpc.loadPackageDefinition(restaurantPackageDefinition).restaurant;
const restaurantClient = new restaurantProto.RestaurantService(
'localhost:50051',
grpc.credentials.createInsecure()
);
const userData = [
{
id: 'user_001',
name: 'John Doe',
email: 'john.doe@example.com',
vistedRestaurant: ['res_001', 'res_002', 'res_003'],
},
];
app.get('/users/:id/visit-restaurants', async (req, res) => {
const userId = req.params.id;
const userDetailData = userData.find((user) => user.id === userId);
restaurantClient.GetRestaurantsBulk(
{
restaurantIds: userDetailData?.vistedRestaurant,
},
(err: grpc.ServiceError | null, response: any) => {
if (err) {
console.error(err);
return res.status(500).json({ message: 'gRPC error' });
}
res.json({
visitedRestaurantsDetail: response.restaurants,
});
}
);
});
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
With the .proto definition, a gRPC server, and a matching client, you’ve transformed a simple REST endpoint into a high‑performance gRPC service. 🎉
Comparison: Express (REST) vs. gRPC
| Feature | REST (JSON over HTTP) | gRPC (Protobuf over HTTP/2) |
|---|---|---|
| Transport | HTTP/1.1 | HTTP/2 |
| Payload Format | JSON | Protobuf (binary) |
| Performance | Moderate | High (binary + HTTP/2) |
| Security | TLS/HTTPS | TLS/HTTPS + built‑in auth |
| Scalability | Good | Excellent for high traffic |
| Tooling | Rich ecosystem | Growing but less mature |
| Learning Curve | Low | Higher (proto files, stubs) |
| Flexibility | Very flexible | Less flexible (strict contracts) |
Other Benefits
- Performance: gRPC is faster than REST because it uses binary encoding and HTTP/2, which allows for better compression and faster data transfer.
- Security: gRPC supports authentication and authorization, making it more secure than REST.
- Scalability: gRPC is designed to handle high traffic and large data sets, which makes it more scalable than REST.
Drawbacks
- Learning Curve: gRPC has a steeper learning curve than REST, which can make it more difficult to learn and implement.
- Tooling: gRPC has limited tooling support compared to REST, which can make debugging and testing more challenging.
- Flexibility: gRPC is less flexible than REST, which can make it harder to adapt to changing requirements.
Conclusion
gRPC is a powerful and efficient way to build microservices, but it is not a one‑size‑fits‑all solution. It shines in high‑performance, low‑latency, and high‑throughput applications.
Please keep in touch
- Portfolio:
- LinkedIn:
- GitHub:
- YouTube:
