Convert Rest API to gRPC
Why use gRPC?
- gRPC is a high-performance, open-source universal RPC framework that runs natively on microservices and serverless architectures.
Example
Rest API
Here is a simple example of a Rest API of restaurant service that return 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");
});
- To convert this Rest API to gRPC, we need to define a .proto file.
.proto file is a language-neutral, platform-neutral, and protocol-neutral interface definition language (IDL) that defines the service and message formats.
See the doc for more details: https://protobuf.dev/programming-guides/proto3/
#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;
}
- a
gRPC serverthat replaces your Express route
Install dependencies
bun add @grpc/grpc-js @grpc/proto-loader
#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
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 server
const server = new grpc.Server();
server.addService(restaurantProto.RestaurantService.service, {
GetRestaurantsBulk: getRestaurantsBulk,
});
// Start server
server.bindAsync(
"0.0.0.0:50051",
grpc.ServerCredentials.createInsecure(),
(port, error) => {
if(error){
console.log("Error binding server", error);
return;
}
console.log("gRPC Server running on port ", port);
server.start();
}
);
- Create a
gRPC clientthat replaces your Express client that returns list of restaurants based on restaurant ids that user visited.
#user-service/grpc-server.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
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;
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');
});
| Express | gRPC |
|---|---|
| POST /restaurants/bulk | GetRestaurantsBulk RPC |
| req.body.restaurantIds | call.request.restaurantIds |
| res.json(result) | callback(null, { restaurants }) |
| JSON over HTTP | Protobuf over HTTP/2 |
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, which makes 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 it more difficult to debug and test.
- Flexibility: gRPC is less flexible than REST, which can make it more difficult 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 is best used for high-performance, low-latency, and high-throughput applications.
=== Done ===
Leave a comment if you have any questions.
===========
Please keep in touch
Portfolio
Linkedin
Github
Youtube
Top comments (0)