Introduction to gRPC: A modern, faster alternative to REST APIs 🚀 !

Sarah Tuer
8 min readApr 10, 2023

--

Hi, techiessss !

Have you ever wondered how your favorite apps and websites are able to communicate with each other seamlessly? How does your banking app know how much money you have in your account, or how does your online shopping cart keep track of the items you’ve added to it? The answer lies in web services, the backbone of modern software development.

Web services allow different applications to communicate with each other over the internet, enabling them to exchange data and perform various operations. However, designing and implementing web services can be a challenging task for developers. They need to consider factors such as performance, scalability, security, and compatibility with different platforms and programming languages.

This is where gRPC comes in, a modern and high-performance Remote Procedure Call (RPC) framework developed by Google. With gRPC, developers can easily create scalable and efficient web services that can be used across different platforms and programming languages.

In this blog post, we’ll explore gRPC in more detail, discussing its architecture, features, and benefits. We’ll also compare it with traditional REST APIs, highlighting the advantages of using gRPC for modern software development. So, if you’re interested in improving your web service development skills, keep reading!

What is REST and gRPC ? ✨

REST: REST APIs are web services that use HTTP and provide an interface for clients to interact with the service. It uses JSON payloads, which can be slow and cumbersome for big datasets. It can also be inefficient for fast-paced or low-latency use cases.

gRPC: a lightweight and high-performance framework that lets you call methods on other machines. This makes it perfect for microservices, which are small independent services that handle specific business tasks and communicate with other services through APIs.

My favorite feature of gRPC is that it allows you to write your code in any language you want, making it a versatile option developers. You code in Python? Your colleague in Go? Your supervisor in Java? You don’t need to hop on to a 3 hour YouTube video for either of these languages. gRPC allows for cross-compatibility for services written in completely different languages. So why settle for outdated APIs when you can embrace the speed and flexibility of gRPC? Get ready to level up your development game!

Contrasting code snippets ✨

Let’s look at a simple example of how gRPC APIs and REST APIs differ in architecture:

Let’s imagine we’re running a pizza delivery service.

With REST APIs, we might have one endpoint to create an order and another to get the order status. Here’s an example in Nest.js:

import { Controller, Get, Post, Body, Param } from '@nestjs/common';

@Controller('orders')
export class OrdersController {
private orders = [];

@Post()
createOrder(@Body() data: any) {
this.orders.push(data);
return data;
}

@Get(':id')
getOrderStatus(@Param('id') orderId: string) {
const order = this.orders.find((o) => o.id === orderId);
if (!order) {
return { error: 'Order not found' };
}
return { status: order.status };
}
}

Now let’s compare that to gRPC. With gRPC, we define our services and messages using Protocol Buffers, and then use those definitions to generate client and server code. Here’s an example:

syntax = "proto3";

package pizza;

service PizzaDelivery {
rpc CreateOrder (OrderRequest) returns (OrderResponse) {}
rpc GetOrderStatus (OrderStatusRequest) returns (OrderStatusResponse) {}
}

message OrderRequest {
string customer_name = 1;
string pizza_type = 2;
}

message OrderResponse {
string order_id = 1;
}

message OrderStatusRequest {
string order_id = 1;
}

message OrderStatusResponse {
string status = 1;
}

We can then use this definition to generate client and server code in JavaScript as well as other languages.

— A quick note: the numbers above simply signify the unique tag numbers assigned to the fields in the message.

gRPC features that outshine REST ✨

gRPC — uses binary data instead of JSON, which means it can be faster and more efficient.

gRPC — has built-in support for things like client-side streaming and bidirectional streaming, which can be more difficult to implement with REST.

gRPC — uses Protocol Buffers, so it’s more strongly typed than REST, which can make it easier to catch errors before they happen.

The API Gateway and it’s relationship with gRPC Micro-services ✨

As I mentioned earlier, gRPC is a Micro-service architecture, if you’re working with large micro-services owned by different Engineers, you’ll most likely need an API gateway.

An API gateway is a server that acts as an entry point for a collection of microservices or APIs (Application Programming Interfaces). It sits between clients (such as web or mobile applications) and the microservices/APIs that provide the required functionality.

Think of the API gateway as the bouncer at the club. It’s the first point of contact for any client (like a web or mobile application) trying to access your microservices. The gateway checks if the client is on the guest list (authorized), then escorts them to the correct microservice with style and grace.

The API Gateway and gRPC Microservices go together like peanut butter and jelly (or garri and groundnuts, if you’ve heard of the concept). If you’re working with a large collection of microservices, each owned by different teams or engineers, you’ll almost certainly need an API Gateway to keep everything running smoothly.

In the context of gRPC, the API Gateway acts as a bridge between clients and your gRPC microservices. It serves as the first point of contact for clients (such as web or mobile applications) trying to access your microservices. The API Gateway can route requests to the correct microservice and perform any necessary transformations or translations on the request or response.

The beauty of using gRPC with an API Gateway is that gRPC’s lightweight and efficient communication protocol makes it ideal for microservices architectures. By using an API Gateway to handle the complexities of client authentication, routing, and load balancing, you can focus on building fast and responsive gRPC microservices that do one thing and do it well.

     +---------------------+
| API Gateway |
+---------------------+
|
+-----------+-----------+
| | |
+---------+ +---------+ +---------+
| gRPC | | gRPC | | gRPC |
| Service | | Service | | Service |
+---------+ +---------+ +---------+

How to use gRPC Micro-services in your app✨

Let’s imagine that we have a pizza delivery system, and we want to use Nest.js to build a micro-service that will handle the order requests.

We start off by installing the gRPC and Micro-service modules:

npm install @nestjs/microservices grpc --save

pizza.proto:

syntax = "proto3"; 

package pizza;

service PizzaService {
rpc GetPizza(GetPizzaRequest)returns (Pizza);
}

message GetPizzaRequest {
string pizzaType = 1;
}

message Pizza {
string name = 1;
string description = 2;
repeated string toppings = 3;
float price = 4;
}

To get started, we’ll create a protocol buffer file called pizza.proto that defines the messages and services our microservice will use. This file is written in the syntax version 3 of Protocol Buffers and has a package name of pizza.

Then we generate TypeScript code from thepizza.proto file:

npx grpc_tools_node_protoc \
--plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts \
--ts_out=./src/proto \
--grpc_out=./src/proto \
--proto_path=./proto \
./proto/pizza.proto

This will generate thepizza.pb.ts and pizza_grpc_pb.d.ts TypeScript files in the src > proto directory. If you’re looking to generate the files in different languages, all you have to do is change the output format by replacing the --ts_out and --grpc_out flags with the appropriate flags for your target language. For example, if you want to generate the files in Python, you can use the following command:

python -m grpc_tools.protoc \ 
--python_out=./src/proto \
--grpc_python_out=./src/proto \
--proto_path=./proto \
./proto/pizza.proto

Your pizza.pb.ts should look like this:

/* eslint-disable */
import { GrpcMethod, GrpcStreamMethod } from "@nestjs/microservices";
import { Observable } from "rxjs";

export const protobufPackage = "pizza";

export interface GetPizzaRequest {
pizzaType: string;
}

export interface Pizza {
name: string;
description: string;
toppings: string[];
price: number;
}

export const _PACKAGE_NAME = "";

export interface PizzaServiceClient {
getPizza(request: GetPizzaRequest): Observable<Pizza>;
}

export interface PizzaServiceController {
getPizza(request: GetPizzaRequest): Promise<Pizza> | Observable<Pizza> | Pizza;
}

export function PizzaServiceControllerMethods() {
return function (constructor: Function) {
const grpcMethods: string[] = ["getPizza"];
for (const method of grpcMethods) {
const descriptor: any = Reflect.getOwnPropertyDescriptor(constructor.prototype, method);
GrpcMethod("PizzaService", method)(constructor.prototype[method], method, descriptor);
}
const grpcStreamMethods: string[] = [];
for (const method of grpcStreamMethods) {
const descriptor: any = Reflect.getOwnPropertyDescriptor(constructor.prototype, method);
GrpcStreamMethod("PizzaService", method)(constructor.prototype[method], method, descriptor);
}
};
}

export const PIZZA_PACKAGE_NAME = "pizza";
export const PIZZA_SERVICE_NAME = "PizzaService";

pizza.controller.ts:

import { Controller } from '@nestjs/common';
import { GrpcMethod } from'@nestjs/microservices';
import { Pizza } from './interfaces/pizza.interface';
import { PizzaService } from './pizza.service';

@Controller()
export class PizzaController {
constructor(private readonly pizzaService: PizzaService) {}

@GrpcMethod('PizzaService')
async getPizza(data: { pizzaType: string }): Promise<Pizza> {
const pizza = await this.pizzaService.getPizza(data.pizzaType);
return pizza;
}
}

Here, we have a PizzaController that handles incoming gRPC requests for pizza orders. It delegates to the PizzaService to provide the response, which returns a pizza object with details about the order. Let's get our taste buds ready and start ordering some delicious pizzas!

pizza.interface.ts:

export interface Pizza { 
name: string;
description: string;
toppings: string[];
price: number;
}

The Pizza interface defines the structure of a Pizza object, including its name, description, toppings, and price. This interface is used in other parts of the code to ensure that data is consistent and compatible.

pizza.service.ts:

import { Inject, Injectable } from '@nestjs/common'; 
import { Pizza } from'./interfaces/pizza.interface';
import { ClientGrpc } from '@nestjs/microservices';
import {
PIZZA_SERVICE_NAME,
PizzaServiceClient,
} from './proto/pizza.pb';

@Injectable()
export class PizzaService {
async getPizza(pizzaType: string): Promise<Pizza> {
private service: PizzaServiceClient;

@Inject(PIZZA_PACKAGE_NAME)
private readonly client: ClientGrpc;

public onModuleInit(): void {
this.service = this.client.getService<PizzaServiceClient>(
PIZZA_SERVICE_NAME,
);
}

constructor() {
super(PizzaService.name);
}

return {
name: 'Margherita', description: 'Tomato sauce, mozzarella, and fresh basil',
toppings: ['Tomato sauce', 'Mozzarella', 'Fresh basil'],
price: 10.99,
};
}
}

PizzaController provides a getPizza method that takes a pizza type and returns a Pizza object with its name, description, toppings, and price. Currently, it always returns a Margherita pizza object with a fixed price and toppings.

pizza.module.ts:

iimport { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { PizzaController } from './pizza.controller';
import { PizzaService } from './pizza.service';
import {
PIZZA_PACKAGE_NAME,
PIZZA_SERVICE_NAME,
} from './proto/pizza.pb';
import * as grpc from '@grpc/grpc-js';

@Module({
imports: [
ClientsModule.registerAsync([
{
name: PIZZA_SERVICE_NAME,
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
transport: Transport.GRPC,
options: {
package: PIZZA_PACKAGE_NAME,
protoPath: 'proto/pizza.proto',
},
})
},
]),
],
controllers: [PizzaController],
providers: [PizzaService],
})
export class PizzaModule {}

The PizzaModule is where the pizza magic happens! It imports the ClientsModule to register the PIZZA_PACKAGE, which uses the GRPC transport protocol and reads the pizza.proto file. The module also sets up the PizzaController to handle incoming requests and the PizzaService to serve delicious pizza data.

main.ts:

import { NestFactory } from '@nestjs/core'; 
import { AppModule } from './app.module';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';

async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Set up a gRPC microservice listener
app.connectMicroservice<MicroserviceOptions>({
transport: Transport.GRPC,
options: {
url: '0.0.0.0:5000',
package: 'pizza',
protoPath: join(__dirname, 'pizza.proto'),
},
});

await app.startAllMicroservicesAsync();

await app.listen(3000);
}

bootstrap();

This is the main file where we start the Nest.js application and connect it to a gRPC microservice. We first import the necessary modules and files. Then, we define an async function bootstrap(), where we create an instance of the AppModule using NestFactory. We also create a gRPC microservice listener and connect it to the app using connectMicroservice() method. Finally, we start both the Nest.js application and the gRPC microservice by calling app.startAllMicroservicesAsync() and app.listen(3000) respectively.

Conclusion ✅

Congratulations, you’ve made it to the end! Now you know the difference between REST and gRPC, and have seen some code examples that illustrate the power of the Google Remote Procedure Call. With features like streaming and bidirectional communication, gRPC truly shines in certain scenarios.

We also talked about the API Gateway, which can help you manage and scale your gRPC microservices. So go ahead and give gRPC a try in your next app, and see what kind of magic you can create!

P. S; if you’re looking for the perfect video to learn about gRPC, I highly recommend this one from Hussein Nasser.

— Sarah.

--

--