Your first gRPC API in Node.js
Cheulong Sear

Cheulong Sear @cheulong

About: Hi, I’m Cheulong Sear. Love learning new staff. I will show my journey in the IT field and what I'm building during my free time.

Location:
Bangkok, Thailand
Joined:
Jun 30, 2024

Your first gRPC API in Node.js

Publish Date: Feb 2
0 0

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");
});

Enter fullscreen mode Exit fullscreen mode
  • 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;
  }
Enter fullscreen mode Exit fullscreen mode
  • a gRPC server that replaces your Express route

Install dependencies

  bun add @grpc/grpc-js @grpc/proto-loader
Enter fullscreen mode Exit fullscreen mode
#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();
  }
);
Enter fullscreen mode Exit fullscreen mode
  • Create a gRPC client that 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');
});
Enter fullscreen mode Exit fullscreen mode
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 ===

Code for this article

Leave a comment if you have any questions.

===========
Please keep in touch
Portfolio
Linkedin
Github
Youtube

Comments 0 total

    Add comment