How to Create an API Using gRPC and Node.js

How to Create an API Using gRPC and Node.js

Posted in

gRPC is a modern open-source RPC framework that can run in any environment. It is used by many large companies, including Google, to power some of their most popular services. gRPC is also an excellent fit for Node.js applications due to its high performance and small footprint.

This guide will show you how to get started with gRPC in Node.js with a simple example. We’ll create a straightforward service that allows us to get information about a user. To do this, we’ll first need to define our service in a .proto file. Then, we’ll generate the necessary Node.js code from our .proto file. Finally, we’ll write a simple Node.js application that uses our service.

With this guide, you’ll be up and running with gRPC in Node.js in no time!

Step 1: Setup and Installation

To start off, you’ll first need to install some dependencies for using gRPC in your project. Also, this article assumes that you have already installed and set up Node.js on your system.

First, create a new folder, then set up a Node.js project with the following command:

npm init -y

Then, install the required gRPC dependencies with the npm package manager:

npm install @grpc/grpc-js @grpc/proto-loader

After this successfully installs, create three more files in the same directory called server.js, client.js, and service_def.proto.

Step 2: Defining Our Service

In this part, we will work with the service_def.proto file we created earlier. With this file, we can describe what our services are, which packages they use, and their input, output, and error types. The .proto file also implements the RPC methods and services for the client — it defines the messages that the server-side and client-side interact with.

syntax = "proto3";

message Empty {}

message User {
    string name = 1;
    int32 age = 2;
}

service UserService {
    rpc GetUser (Empty) returns (User) {}
}

Code Explanation

Here we have defined a data model called User, with the two fields name and age. The UserService service is a collection(s) of signatures of the different actions this service has available and what they return in the response.

Now that we have defined our services, the same configuration file can then be implemented using Go, Rust, Python, and Java without any required modifications! This is a compelling aspect of gRPC. You can define a profile with the RPCs and their interface, their input and output types, and any server-specific configuration.

The client can then use the profile to generate stubs and skeletons of the client and server sides. The client and server can share the same profile and the same definitions of the RPCs. Now anyone can use that proto file to write the implementations of this service, but let’s make an implementation ourselves.

Implementing the Service

Now that we have defined our services, we can test them out by creating our own server and client program. Doing this lets us see how the server responds to a client request.

Note: All of the following code is explained in the comments within the code.

Step 3: Creating Server

To create the server, open up the server.js file and load up the gRPC dependencies and the .proto file created earlier:

//dependencies
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");

//path to our proto file
const PROTO_FILE = "./service_def.proto";

Now let’s implement the service defined in our .proto file and its GetUser procedure:

//options needed for loading Proto file
const options = {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
};


const pkgDefs = protoLoader.loadSync(PROTO_FILE, options);

//load Definition into gRPC
const userProto = grpc.loadPackageDefinition(pkgDefs);

//create gRPC server
const server = new grpc.Server();

//implement UserService
server.addService(userProto.UserService.service, {
  //implment GetUser
  GetUser: (input, callback) => {
    try {
      callback(null, { name: "Jake", age: 25 });
    } catch (error) {
      callback(error, null);
    }
  },
});


//start the Server
server.bindAsync(
  //port to serve on
  "127.0.0.1:5000",
  //authentication settings
  grpc.ServerCredentials.createInsecure(),
  //server start callback 
  (error, port) => {
    console.log(`listening on port ${port}`);
    server.start();
  }
);

Now that the server is running and listening for requests, we can define a client to send requests and communicate to the server.

The client and the server are in separate files and are independent of one another. After running the server, you can run the client by typing in node client.js.

Step 4: Creating a Client

Now to create the client, the process would be similar to creating the server except for the last part. First, open up the client.js file and import the dependencies:

//dependencies
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");

//path to our proto file
const PROTO_FILE = "./service_def.proto";

Then create the client and make a request to the GetDog Procedure:

//options needed for loading Proto file
const options = {
  keepCase: true,
  longs: String,
  enums: String,
  defaults: true,
  oneofs: true,
};

const pkgDefs = protoLoader.loadSync(PROTO_FILE, options);

//load Definition into gRPC
const UserService = grpc.loadPackageDefinition(pkgDefs).UserService;

//create the Client
const client = new UserService(
  "localhost:3500",
  grpc.credentials.createInsecure()
);

//make a call to GetUser
client.GetUser({}, (error, user) => {
  if (error) {
    console.log(error);
  } else {
    console.log(user);
  }
});
`

Output

Now to see if everything actually works, open up two terminals — the first one starts the server using the following command:

node server.js

Then the second one runs the client with the following command:

node client.js

And you should see a result like this:

output grpc server

Final Words

That’s it. We’ve explained the basics of creating an API with gRPC and Node.js, how to build a service server, how to build a client that can find those services, and how to wire them together. To reiterate, the benefit of this approach is you can have one definition of a service (the .proto file), which can be implemented many times but work with the same client since all server implementations are bound by the profile.

This approach also allows you to use a service implementation in multiple applications. For example, if you have a service that provides a user interface or back-end logic, you can use the same service implementation in multiple applications! This is particularly useful if you have an existing application that you want to enhance with new functionality.