DEV Community

Diego Liascovich
Diego Liascovich

Posted on

Building a Clean gRPC API in Node.js

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 BookService API 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
Enter fullscreen mode Exit fullscreen mode

πŸ“„ 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;
}
Enter fullscreen mode Exit fullscreen mode

🧠 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;
Enter fullscreen mode Exit fullscreen mode

πŸ—οΈ 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();
Enter fullscreen mode Exit fullscreen mode

πŸ’Ό 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();
Enter fullscreen mode Exit fullscreen mode

πŸ”Œ 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 };
Enter fullscreen mode Exit fullscreen mode

πŸ“¦ 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;
Enter fullscreen mode Exit fullscreen mode

πŸ–₯️ 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();
Enter fullscreen mode Exit fullscreen mode

🐳 Docker Setup

Dockerfile

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 50051
CMD ["npm", "start"]
Enter fullscreen mode Exit fullscreen mode

docker-compose.yml

version: "3.9"

services:
  grpc-books:
    build: .
    ports:
      - "50051:50051"
Enter fullscreen mode Exit fullscreen mode

πŸ§ͺ Testing with BloomRPC

  1. Open BloomRPC.
  2. Load the book.proto file.
  3. Set server to localhost:50051.
  4. Use:
    • GetBook β†’ payload: { "id": "1" }
    • ListBooks β†’ payload: {}

πŸ“ Source Code

πŸ‘‰ View the full repository here

Top comments (0)