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 »

Stop Writing OpenAPI Specs by Hand

The Problem with Manual OpenAPI Specs Writing API documentation is tedious. After building an endpoint, you often spend minutes crafting YAML that quickly beco...

Python 3.13 and 3.14 are now available

Builds and Functions now support Python 3.13 and Python 3.14 alongside the previously supported Python 3.12. Projects without a specified Python version continu...