Friday, April 5, 2024

Getting Started with gRPC in Node.js


 gRPC shines as a high-performance, open-source framework developed by Google. It boasts features like bidirectional streaming, authentication, and efficient communication over HTTP/2, making it a go-to choice for building scalable and robust distributed systems. Let's embark on a journey to explore the fundamentals of gRPC and how to implement it in Node.js.

Introduction to gRPC

gRPC leverages HTTP/2 for transport and Protocol Buffers for interface description, making it a modern and lightweight RPC framework. Its key features include bidirectional streaming, flow control, and support for blocking and nonblocking bindings.

Why Use Protocol Buffers?

Protocol Buffers, or Protobuf, serve as Google's language-neutral serialization mechanism, offering compact and efficient data exchange. Compared to traditional formats like XML or JSON, Protobuf offers smaller payloads, efficient binary serialization, and broad language support.

gRPC vs REST

While REST relies on HTTP/1.1 for communication, gRPC utilizes the advanced features of HTTP/2, enabling efficient bidirectional streaming and multiplexing. This allows for more versatile communication patterns like client streaming, server streaming, and bidirectional streaming.

Setting Up gRPC in Node.js

To begin, we need to define our message formats and services using Protocol Buffers. Let's create a todo.proto file to describe our Todo application's services:

syntax = "proto3";

package todoPackage;

service Todo {

 rpc createTodo(TodoItem) returns (TodoItem);

 rpc readTodos(voidNoParams) returns (TodoItems);

 rpc readTodosStream(voidNoParams) returns (stream TodoItem);

}

message voidNoParams{}

message TodoItem{

 required int32 id=1;

 required string text =2;

}

message TodoItems{

 repeated TodoItem items = 1;

}

Implementing the Server in Node.js

With our todo.proto file defined, we can now implement the server-side logic in Node.js. Create a server.js file and populate it with the following code:

const grpc = require("grpc");

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

// load the proto file

const packageDefinition = protoLoader.loadSync("todo.proto", {

 keepCase: false,

 longs: String,

 enums: String,

 defaults: true,

 oneofs: true,

});

// Load gRPC package definition as a gRPC object

const grpcObject = grpc.loadPackageDefinition(packageDefinition);

// Get our todoPackage from our todo.proto file

const todoPackage = grpcObject.todoPackage;

// Create a gRPC server

const server = new grpc.Server();

// Set the port, our server will be listening on

server.bind("0.0.0.0:5000", grpc.ServerCredentials.createInsecure());

// Map the services with their implementations

server.addService(todoPackage.Todo.service, {

 createTodo: createTodo,

 readTodos: readTodos,

 readTodosStream: readTodosStream,

});

// start the server

server.start();

// temporarily stores our todos

const todos = [];

function createTodo(call, callback) {

 // console.log(call);

 const todoText = call.request.text;

 const todoItem = {

 id: todos.length + 1,

 text: todoText,

 };

 todos.push(todoItem);

// Send reply back to client using callback(error, response)

 callback(null, todoItem);

}

function readTodos(call, callback) {

 callback(null, { items: todos });

}

function readTodosStream(call, callback) {

 todos.forEach((todo) => call.write(todo));

 call.end();

}

Implementing the Client in Node.js

Next, let's implement the client-side logic in Node.js. Create a client.js file and populate it with the following code:

const grpc = require("grpc");

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

// load the proto file

const packageDefinition = protoLoader.loadSync("todo.proto", {

 keepCase: false,

 longs: String,

 enums: String,

 defaults: true,

 oneofs: true,

});

// Load a gRPC package definition as a gRPC object

const grpcObject = grpc.loadPackageDefinition(packageDefinition);

// Get our todoPackage from our todo.proto file

const todoPackage = grpcObject.todoPackage;

// Initiate client

const client = new todoPackage.Todo(

 "localhost:5000",

 grpc.credentials.createInsecure()

);

// Read the text for our todo from commandline

// Ex: node client.js "Do Laundry"

// Here argv[0] = node, argv[1]=client.js and argv[2]="Do Laundry"

const todoText = process.argv[2];

// create a todo

client.createTodo(

 {

 id: -1,

 text: todoText,

 },

 (err, response) => {

 if (err) {

 console.log(err);

 return;

 }

 console.log(`Succesfully Created a ${response.text} Todo`);

 readTodos();

 readTodosStream();

 }

);

// Read Todos

function readTodos() {

 client.readTodos({}, (err, response) => {

 if (err) {

 console.log(err);

 return;

 }

 console.log("Todos List:\n " + JSON.stringify(response));

 console.log("\n");

 });

}

// readTodos as a stream

function readTodosStream() {

 const call = client.readTodosStream();

call.on("data", (todo) => {

 console.log("Todo Stream: " + JSON.stringify(todo));

 });

call.on("end", () => {

 console.log("Server done!");

 });

 console.log("\n");

}

Running the Application

Once we've implemented both the server and client sides, we can run our Todo application. First, start the server by running node server.js, then create a Todo item using the client by running node client.js "Your Todo Text". You can also retrieve Todos and stream them using appropriate client methods.

With gRPC, building efficient and scalable distributed systems in Node.js becomes a breeze. Its support for bidirectional streaming, Protocol Buffers, and advanced communication patterns empowers developers to create robust and performant applications. So, why wait? Dive into the world of gRPC and unlock the full potential of your distributed systems!

0 comments:

Post a Comment