DEV Community

Cover image for Your first gRPC API in Node.js
Cheulong Sear
Cheulong Sear

Posted on

Your first gRPC API in Node.js

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

Top comments (0)