Your first gRPC API in Node.js

Published: (February 2, 2026 at 12:08 AM EST)
5 min read
Source: Dev.to

Source: Dev.to

Cover image for Your first gRPC API in Node.js

Cheulong Sear

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

FeatureREST (JSON over HTTP)gRPC (Protobuf over HTTP/2)
TransportHTTP/1.1HTTP/2
Payload FormatJSONProtobuf (binary)
PerformanceModerateHigh (binary + HTTP/2)
SecurityTLS/HTTPSTLS/HTTPS + built‑in auth
ScalabilityGoodExcellent for high traffic
ToolingRich ecosystemGrowing but less mature
Learning CurveLowHigher (proto files, stubs)
FlexibilityVery flexibleLess 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:
Back to Blog

Related posts

Read more »

Zero-configuration support for Koa

Vercel now supports applications, an expressive HTTP middleware framework to make web applications and APIs more enjoyable to write, with zero-configuration.Koa...