Skip to content

marcuwynu23/nodejs-grpc-sample

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Node.js gRPC Sample

This project demonstrates how to implement gRPC services using Node.js and TypeScript with service-to-service communication.

Project Structure

nodejs-grpc-sample/
├── README.md              # This file
├── app/                   # Application utilities and shared resources
│   ├── package.json       # Shared dependencies
│   ├── service.proto      # Protocol buffer definitions
│   └── test-services.ts   # Service-to-service testing utility
├── service1/              # UserService (gRPC server)
│   ├── server.ts          # UserService implementation
│   ├── package.json       # Dependencies and scripts
│   └── tsconfig.json      # TypeScript configuration
└── service2/              # ProductService (gRPC server)
    ├── server.ts          # ProductService implementation
    ├── package.json       # Dependencies and scripts
    └── tsconfig.json      # TypeScript configuration

Services

UserService (Port 50051)

  • GetUser - Get a user by ID
  • CreateUser - Create a new user
  • ListUsers - List all users with pagination
  • GetUserWithProducts - Get user with all products (calls ProductService)

ProductService (Port 50052)

  • GetProduct - Get a product by ID
  • CreateProduct - Create a new product
  • ListProducts - List all products with pagination
  • GetProductWithUsers - Get product with all users (calls UserService)

Service-to-Service Communication

This architecture demonstrates bidirectional gRPC communication between services:

  1. UserService → ProductService: When GetUserWithProducts is called, UserService calls ProductService to get all products
  2. ProductService → UserService: When GetProductWithUsers is called, ProductService calls UserService to get all users

How It Works

1. Service Initialization

  • Protocol Buffers: Both services load the same service.proto file from the app/ directory
  • gRPC Servers: Each service starts on its own port (50051 for UserService, 50052 for ProductService)
  • Logging: Comprehensive logging tracks all requests, responses, and service-to-service calls

2. Request Processing Flow

Direct Client Requests:

Client → gRPC Request → Service → Business Logic → Database (in-memory arrays) → gRPC Response → Client

Service-to-Service Communication:

Client → Service A → gRPC Client → Service B → Business Logic → Database → gRPC Response → Service A → Combined Response → Client

3. Promise-Based Architecture

Modern Async/Await Pattern:

// Instead of callbacks:
serviceClient.method(request, (err, response) => {
  if (err) { /* handle error */ }
  else { /* handle response */ }
});

// We use Promise-based pattern:
const response = await new Promise((resolve, reject) => {
  serviceClient.method(request, (err, response) => {
    if (err) reject(err);
    else resolve(response);
  });
});
// Now we can use try/catch and async/await

4. Data Flow Examples

GetUserWithProducts Flow:

  1. Client calls UserService.GetUserWithProducts({ user_id: "1" })
  2. UserService finds user in local array
  3. UserService creates gRPC client to ProductService
  4. UserService calls ProductService.ListProducts({ page: 1, limit: 10 })
  5. ProductService returns products array
  6. UserService combines user + products data
  7. UserService returns combined response to client

Error Handling:

  • gRPC Status Codes: Uses standard gRPC status codes (NOT_FOUND, INTERNAL, etc.)
  • Promise Rejection: Errors are properly caught and re-thrown with context
  • Comprehensive Logging: All operations are logged with timestamps and context

Architecture Diagram

graph TB
    subgraph "Client Layer"
        Client[Client/Test Utility]
    end
    
    subgraph "Service Layer"
        UserService[UserService<br/>Port 50051]
        ProductService[ProductService<br/>Port 50052]
    end
    
    subgraph "Data Layer"
        UserData[(Users Array)]
        ProductData[(Products Array)]
    end
    
    %% Service-to-Service Communication
    UserService -.->|GetUserWithProducts<br/>user_id: 1|ProductService
    ProductService -.->|GetProductWithUsers<br/>product_id: 1|UserService
    
    %% Client to Service Communication
    Client -->|GetUser|UserService
    Client -->|CreateUser|UserService
    Client -->|ListUsers|UserService
    Client -->|GetProduct|ProductService
    Client -->|CreateProduct|ProductService
    Client -->|ListProducts|ProductService
    Client -->|GetUserWithProducts|UserService
    Client -->|GetProductWithUsers|ProductService
    
    %% Service to Data Communication
    UserService -->|CRUD Operations|UserData
    ProductService -->|CRUD Operations|ProductData
    
    %% Styling
    classDef service fill:#232323,stroke:#023047,color:#ffffff
    classDef data fill:#f3f4f6,stroke:#6c757d,color:#000000
    classDef client fill:#28a745,stroke:#1e7e34,color:#ffffff
    
    class UserService,ProductService service
    class UserData,ProductData data
    class Client client
Loading

Setup

1. Initialize App Module

cd app
npm install
cd ..

2. Install Dependencies for Services

cd service1
npm install

cd ../service2
npm install

3. Automation with Auto

For automated development and testing, use the Auto tool:

Install Auto:

# Download the latest release binary from GitHub
# Visit: https://github.com/marcuwynu23/Auto/releases
# Download the appropriate binary for your platform
# Add the binary to your PATH

Available Commands:

# Start both services in development mode and run tests
auto dev

Manual Commands (without Auto):

# Development mode (with auto-reload)
cd service1 && npm run dev
cd service2 && npm run dev
cd app && npm run dev

Running the Services

Start Both Services

Important: Both services must be running for service-to-service communication to work.

  1. Start UserService (Terminal 1):
cd service1
npm start

The service will start on localhost:50051

  1. Start ProductService (Terminal 2):
cd service2
npm start

The service will start on localhost:50052

Start in Development Mode

Use npm run dev to start with auto-reload on file changes.

Testing Service-to-Service Communication

Using Test Utility (Recommended)

The project includes a test utility that demonstrates service-to-service communication:

# Make sure both services are running, then run:
npx tsx app/test-services.ts

This will test both directions of communication:

  • UserService calling ProductService
  • ProductService calling UserService

Using grpcurl

Install grpcurl for testing:

go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest

Test UserService calling ProductService:

# Get user with products (UserService calls ProductService)
grpcurl -plaintext -d '{"user_id": "1"}' localhost:50051 services.UserService/GetUserWithProducts

Test ProductService calling UserService:

# Get product with users (ProductService calls UserService)
grpcurl -plaintext -d '{"product_id": "1"}' localhost:50052 services.ProductService/GetProductWithUsers

Test Individual Services

UserService operations:

# Get a user
grpcurl -plaintext -d '{"user_id": "1"}' localhost:50051 services.UserService/GetUser

# List all users
grpcurl -plaintext -d '{"page": 1, "limit": 10}' localhost:50051 services.UserService/ListUsers

ProductService operations:

# Get a product
grpcurl -plaintext -d '{"product_id": "1"}' localhost:50052 services.ProductService/GetProduct

# List all products
grpcurl -plaintext -d '{"page": 1, "limit": 10}' localhost:50052 services.ProductService/ListProducts

Protocol Buffer Definitions

The service.proto file contains the service definitions and message types for both services. Both services share the same proto file to ensure type safety and consistency.

Sample Data

Both services come with pre-populated sample data:

UserService

ProductService

  • Product ID 1: Laptop ($1299.99)
  • Product ID 2: Mouse ($49.99)

Development Notes

  • Uses TypeScript for type safety
  • Uses ES modules ("type": "module" in package.json)
  • Uses tsx for running TypeScript files directly
  • In-memory data storage (data resets on server restart)
  • gRPC servers use insecure credentials (for development only)

Next Steps

To extend this example:

  1. Add database persistence
  2. Implement authentication/authorization
  3. Add SSL/TLS for secure communication
  4. Add streaming RPCs
  5. Add interceptors for logging/metrics
  6. Add comprehensive error handling

About

This project demonstrates how to implement gRPC services using Node.js and TypeScript with service-to-service communication.

Topics

Resources

Stars

Watchers

Forks

Used by

Contributors

Languages