By Diego Liascovich
Full-Stack Developer | Microservices | Angular | Node.js
gRPC is a high-performance, language-agnostic RPC framework developed by Google. It allows different services or applicationsβpossibly written in different languagesβto communicate through well-defined contracts using Protocol Buffers.
In this post, we'll cover:
- What gRPC is and how it works
- Why it's useful in modern backend architectures
- How to implement a simple
BookServiceAPI using Node.js, gRPC, Docker, and Clean Architecture principles
π What Is gRPC?
gRPC stands for gRPC Remote Procedure Calls. It uses HTTP/2 for transport, Protocol Buffers (protobuf) as the interface definition language, and supports multiple languages.
π§± Components of gRPC
- .proto file: Defines services and message schemas
- Server: Implements service logic
- Client: Invokes remote methods
- Protocol Buffers: Efficient binary serialization format
βοΈ Why Use gRPC?
- β Strongly-typed messages (via Protobuf)
- β Efficient binary communication
- β Multi-language support (Node, Go, Java, Python, etc.)
- β Streaming support (client/server/bidirectional)
- β Ideal for microservices in internal networks
π Our Use Case: Book Service
Weβll build a simple BookService API with two RPC methods:
-
GetBook(id): returns a book by ID -
ListBooks(): returns all books
All data will be mocked in memory, simulating a database.
ποΈ Project Structure
grpc-books/
βββ proto/
β βββ book.proto
βββ proto-loader.js
βββ src/
β βββ application/
β β βββ bookService.js
β βββ domain/
β β βββ book.js
β βββ infrastructure/
β β βββ bookRepository.js
β βββ interfaces/
β β βββ grpc/
β β βββ bookHandler.js
β βββ server.js
βββ Dockerfile
βββ docker-compose.yml
βββ package.json
π book.proto
syntax = "proto3";
package books;
service BookService {
rpc GetBook (GetBookRequest) returns (Book);
rpc ListBooks (Empty) returns (BookList);
}
message GetBookRequest {
string id = 1;
}
message Empty {}
message Book {
string id = 1;
string title = 2;
string author = 3;
int32 year = 4;
}
message BookList {
repeated Book books = 1;
}
π§ Domain: Book Entity
// src/domain/book.js
class Book {
constructor(id, title, author, year) {
this.id = id;
this.title = title;
this.author = author;
this.year = year;
}
}
module.exports = Book;
ποΈ Infrastructure: Mock Repository
// src/infrastructure/bookRepository.js
const Book = require('../domain/book');
const books = [
new Book('1', '1984', 'George Orwell', 1949),
new Book('2', 'The Hobbit', 'J.R.R. Tolkien', 1937),
new Book('3', 'Clean Code', 'Robert C. Martin', 2008),
];
class BookRepository {
findById(id) {
return books.find(b => b.id === id);
}
findAll() {
return books;
}
}
module.exports = new BookRepository();
πΌ Application: Book Service
// src/application/bookService.js
const bookRepo = require('../infrastructure/bookRepository');
class BookService {
getBook(id) {
const book = bookRepo.findById(id);
if (!book) throw new Error('Book not found');
return book;
}
listBooks() {
return bookRepo.findAll();
}
}
module.exports = new BookService();
π Interface: gRPC Handler
// src/interfaces/grpc/bookHandler.js
const bookService = require('../../application/bookService');
function GetBook(call, callback) {
try {
const book = bookService.getBook(call.request.id);
callback(null, book);
} catch (err) {
callback({ code: 5, message: err.message }); // NOT_FOUND
}
}
function ListBooks(call, callback) {
const books = bookService.listBooks();
callback(null, { books });
}
module.exports = { GetBook, ListBooks };
π¦ Proto Loader
// proto-loader.js
const path = require('path');
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const PROTO_PATH = path.join(__dirname, 'proto', 'book.proto');
const packageDefinition = protoLoader.loadSync(PROTO_PATH, {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
module.exports = grpc.loadPackageDefinition(packageDefinition).books;
π₯οΈ Server
// src/server.js
const grpc = require('@grpc/grpc-js');
const proto = require('../proto-loader');
const bookHandler = require('./interfaces/grpc/bookHandler');
function main() {
const server = new grpc.Server();
server.addService(proto.BookService.service, {
GetBook: bookHandler.GetBook,
ListBooks: bookHandler.ListBooks,
});
const address = '0.0.0.0:50051';
server.bindAsync(address, grpc.ServerCredentials.createInsecure(), () => {
console.log(`π gRPC server running at ${address}`);
server.start();
});
}
main();
π³ Docker Setup
Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 50051
CMD ["npm", "start"]
docker-compose.yml
version: "3.9"
services:
grpc-books:
build: .
ports:
- "50051:50051"
π§ͺ Testing with BloomRPC
- Open BloomRPC.
- Load the
book.protofile. - Set server to
localhost:50051. - Use:
-
GetBookβ payload:{ "id": "1" } -
ListBooksβ payload:{}
-
Top comments (0)