Skip to main content

System Architecture

The microservices platform follows a distributed architecture with polyglot services communicating via gRPC/Connect and REST APIs, backed by PostgreSQL for data persistence and Traefik for edge routing.

Service Directory

Go Microservices (connect-go)

All Go services use connect-go, a gRPC-compatible RPC framework that works over HTTP/1.1, HTTP/2, and gRPC protocols.

Greeter Service

Port: 8080
Protocol Buffers: greeter/v1/greeter.proto
Demonstrates service-to-service calls and external API integration.Responsibilities:
  • Accept greeting requests
  • Call Caller service for external API data
  • Store greetings in PostgreSQL
  • Return formatted response with external API status
Database: greeter_db
Location: services/internal/greeter/

Caller Service

Port: 8081
Protocol Buffers: caller/v1/caller.proto
HTTP client service for external API calls.Responsibilities:
  • Receive URL from other services
  • Make HTTP requests to external APIs
  • Return status code and response metadata
  • Track call history in database
Database: caller_db
Location: services/internal/caller/

Gateway Service

Port: 8082
Protocol Buffers: gateway/v1/gateway.proto
API gateway integrating with custom language service.Responsibilities:
  • Route requests to custom-lang-service
  • Transform REST responses
  • Aggregate data from Node.js services
  • Persist gateway metrics
Database: gateway_db
Location: services/internal/gateway/

Node.js Microservices (Express)

Auth Service

Port: 8090
Framework: Express 5
JWT-based authentication service.Features:
  • User registration with bcrypt password hashing
  • JWT token generation and validation
  • Login/signup endpoints at /auth/*
  • Password strength validation
  • Token expiration handling
Database: auth_db
Location: node-services/auth-service/
Tech Stack:
  • express - Web framework
  • jsonwebtoken - JWT handling
  • bcrypt - Password hashing
  • pg - PostgreSQL client

Custom Lang Service

Port: 3000
Framework: Express 5
Custom language processing service.Responsibilities:
  • Process custom language requests
  • Return localized responses
  • Store language preferences
  • Expose REST API for Gateway service
Database: lang_db
Location: node-services/custom-lang-service/

Communication Patterns

1. Frontend to Backend (connect-web)

The React frontend uses connect-query (TanStack Query + connect-web) for type-safe API calls.
// Auto-generated from proto files
import { useGreet } from "@/gen/greeter/v1/greeter-GreeterService_connectquery";

function GreeterDemo() {
  const greet = useGreet();
  
  const handleGreet = async (name: string) => {
    const result = await greet.mutateAsync({ name });
    console.log(result.message); // Fully typed!
  };
}
All frontend API code is generated from .proto files via buf generate, ensuring type safety across the stack.

2. Service-to-Service (connect-go)

Go services communicate using connect-go clients:
services/internal/greeter/service.go
func (s *Service) Greet(ctx context.Context, req *connect.Request[greeterv1.GreetRequest]) (*connect.Response[greeterv1.GreetResponse], error) {
    name := req.Msg.GetName()
    
    // Call Caller service
    callerResp, err := s.callerClient.CallExternal(
        ctx, 
        connect.NewRequest(&callerv1.CallExternalRequest{
            Url: s.externalURL,
        }),
    )
    
    if err != nil {
        return nil, connect.NewError(connect.CodeUnavailable, err)
    }
    
    return connect.NewResponse(&greeterv1.GreetResponse{
        Message:            fmt.Sprintf("Hello %s!", name),
        ExternalStatus:     callerResp.Msg.GetStatusCode(),
        ExternalBodyLength: callerResp.Msg.GetBodyLength(),
    }), nil
}

3. REST Integration

Gateway service calls Node.js services via REST:
resp, err := http.Get(customLangBaseURL + "/api/lang")

4. Protocol Buffers Schema

Example proto definition for Greeter service:
proto/greeter/v1/greeter.proto
syntax = "proto3";

package greeter.v1;

service GreeterService {
  rpc Greet(GreetRequest) returns (GreetResponse) {}
}

message GreetRequest {
  string name = 1;
}

message GreetResponse {
  string message = 1;
  int32 external_status = 2;
  int32 external_body_length = 3;
}
Changes to .proto files require running buf generate to regenerate Go and TypeScript clients.

Data Layer

PostgreSQL Architecture

Each service has its own database following the Database-per-Service pattern:
ServiceDatabase NamePurpose
Greetergreeter_dbStore greeting records with external API metadata
Callercaller_dbTrack external API call history
Gatewaygateway_dbGateway request/response logs
Authauth_dbUser credentials and sessions
Custom Langlang_dbLanguage preferences and translations
Connection String Format:
postgresql://devuser:devpass@postgres:5432/<database_name>
Migrations: Each service manages its own schema migrations.
In production, each service should have separate PostgreSQL instances or schemas to ensure true isolation and independent scaling.

Database Initialization

Databases are created automatically via scripts/init-db.sh on first Docker Compose startup:
scripts/init-db.sh
#!/bin/bash
set -e

psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
    CREATE DATABASE greeter_db;
    CREATE DATABASE caller_db;
    CREATE DATABASE gateway_db;
    CREATE DATABASE auth_db;
    CREATE DATABASE lang_db;
EOSQL

Infrastructure Components

Traefik v3 (API Gateway)

Traefik acts as the edge router and API gateway with: Features:
  • Automatic service discovery via Docker labels
  • HTTP/2 (h2c) support for connect-go services
  • Middleware injection (CORS, auth, rate limiting)
  • Request/response logging
  • Health checks
Configuration (docker-compose.yml):
labels:
  - "traefik.enable=true"
  - "traefik.http.routers.greeter.rule=PathPrefix(`/greeter.v1.GreeterService`)"
  - "traefik.http.routers.greeter.middlewares=cors@file,auth@file,rate-limit@file"
  - "traefik.http.services.greeter.loadbalancer.server.scheme=h2c"
Access Points:

Observability Stack (Kubernetes Only)

When running with full-bootstrap, you get:

Prometheus

Port: 30090
Metrics collection and alerting
  • Service metrics
  • Go runtime metrics
  • Node.js metrics
  • Custom business metrics

Grafana

Port: 30300
Credentials: admin/admin
Visualization and dashboards
  • Pre-configured dashboards
  • Service health monitoring
  • Query Prometheus and Loki

Loki

Log aggregation system
  • Centralized logging
  • Log correlation with traces
  • Efficient storage
  • LogQL queries

Tempo

Distributed tracing backend
  • OpenTelemetry integration
  • End-to-end request tracing
  • Service dependency mapping
  • Latency analysis
OpenTelemetry Integration: All services export traces and metrics:
docker-compose.yml
environment:
  OTEL_EXPORTER_OTLP_ENDPOINT: "http://tempo:4317"
  OTEL_SERVICE_NAME: "greeter-service"
In local Docker Compose mode, OTEL_EXPORTER_OTLP_ENDPOINT is set to "" to disable telemetry export.

Deployment Models

Docker Compose (Local Development)

Use Case: Quick local testing without Kubernetes complexity. Architecture:
  • All services in a single app bridge network
  • Traefik for routing
  • Persistent PostgreSQL volume
  • No observability stack
Start:
docker compose up

Kubernetes with Tilt (Cloud-Native Development)

Use Case: Develop and test in a Kubernetes environment locally. Architecture:
  • Kind cluster (local Kubernetes)
  • Tilt for live reloading
  • nixidy for manifest generation
  • Optional observability stack
Start:
tilt up
Tilt Features:
  • Live code reloading without rebuilding containers
  • Dependency management between services
  • Log streaming from all pods
  • Resource status dashboard at http://localhost:10350

Infrastructure as Code (nixidy)

Kubernetes manifests are generated from Nix modules:
deploy/k8s/greeter.nix
{ config, ... }:
{
  applications.greeter-service = {
    namespace = "default";
    deployment = {
      replicas = 1;
      image = "greeter:latest";
      port = 8080;
    };
    service = {
      type = "ClusterIP";
      port = 8080;
    };
  };
}
Generate manifests:
gen-manifests  # Outputs to deploy/manifests/
Manifest generation requires files to be in Git staging. Run git add <files> before gen-manifests.

Security Considerations

Authentication

  • JWT tokens with expiration
  • bcrypt password hashing (cost factor 10)
  • Auth middleware via Traefik
  • Token validation on protected routes

Network Security

  • Services isolated in container networks
  • Traefik as single entry point
  • CORS middleware configured
  • Rate limiting enabled

Data Protection

  • Database credentials via environment variables
  • SQL injection prevention via parameterized queries
  • Separate databases per service
  • Connection pooling with pgx (Go) and pg (Node.js)

Development Best Practices

  • .env files in .gitignore
  • Pre-commit hooks for security scanning
  • Dependency vulnerability checks
  • Container scanning in CI
Production Deployment: Never use development secrets (JWT_SECRET=dev-secret, POSTGRES_PASSWORD=devpass) in production. Use proper secret management tools like Vault, AWS Secrets Manager, or Kubernetes Secrets.

Directory Structure

.
├── services/              # Go microservices
│   ├── cmd/               # Service entry points
│   │   ├── greeter/
│   │   ├── caller/
│   │   └── gateway/
│   ├── internal/          # Service implementations
│   │   ├── greeter/
│   │   ├── caller/
│   │   ├── gateway/
│   │   └── platform/      # Shared utilities (DB, logging, OTEL)
│   └── gen/go/            # Generated protobuf code

├── node-services/         # Node.js microservices
│   ├── auth-service/
│   └── custom-lang-service/

├── frontend/              # React application
│   ├── src/
│   │   ├── app/           # App providers and routing
│   │   ├── features/      # Feature modules
│   │   │   ├── auth/
│   │   │   ├── greeter/
│   │   │   └── gateway/
│   │   ├── gen/           # Generated protobuf + connect-query
│   │   ├── interceptors/  # connect-rpc interceptors
│   │   └── lib/           # Utilities
│   └── package.json

├── proto/                 # Protocol Buffers definitions
│   ├── greeter/v1/
│   ├── caller/v1/
│   └── gateway/v1/

├── deploy/
│   ├── docker/            # Dockerfiles
│   ├── k8s/               # nixidy Kubernetes modules
│   ├── manifests/         # Generated YAML (gitignored)
│   ├── nixidy/            # nixidy environment configs
│   └── traefik/           # Traefik configuration

├── scripts/               # Utility scripts
├── docs/                  # Style guides
├── docker-compose.yml     # Local development setup
├── Tiltfile               # Tilt configuration
├── buf.yaml               # buf configuration
└── devenv.nix             # Nix development environment

Next Steps

Adding a New Service

Learn how to scaffold new Go or Node.js microservices

Protocol Buffers Guide

Master proto definitions and code generation

Testing Strategy

Unit, integration, and smoke testing patterns

CI/CD Pipeline

Understand the GitHub Actions workflow

Build docs developers (and LLMs) love