psenger/python-nodejs-bridge-poc
Faced with the fact that NodeJS just is a better Web Application and Python a Better AI app, how can we tie them together
Node.js + Python Bridge
A hybrid architecture that refuses to compromise.
Stop choosing between Node.js and Python. Use both.
Building agentic chat backends means choosing: Node.js for streaming and type safety, or Python for AI orchestration. This project proves you don't have to pick sides.
The Problem
Building a production-grade conversational AI backend demands two things that live in different ecosystems:
| Requirement | Best Tool | Why |
|---|---|---|
| Multi-format delivery | Node.js | Content negotiation across MIME types (SSE, JSON, Markdown, plain text) with Accept header routing |
| SSE streaming to clients | Node.js | Async-native event loop, res.write() direct socket control, zero silent blocking |
| Hexagonal architecture | TypeScript | Structural typing makes ports discoverable, safe, and refactorable at compile time |
| LLM agent orchestration | Python | LangGraph, LangChain, CrewAI — the AI ecosystem lives here |
| LLM observability | Python | LangFuse tracing, token cost tracking, prompt versioning |
Pick one language? You lose half the table. This POC validates that you can have the entire table.
The Solution
A bridge architecture where each language owns what it does best, connected by an internal HTTP sidecar with sub-millisecond overhead:
┌──────────────────────────────────────────┐
│ Node.js (Express + TypeScript) │
│ │
Client ────────▶│ Content Negotiation (Accept header) │
▲ │ ┌─────────┬──────┬──────────┬────────┐ │
│ │ │text/SSE │ JSON │ Markdown │ Text │ │
│ │ └─────────┴──────┴──────────┴────────┘ │
│ │ HTTP Routing · Session Mgmt │
│ │ Hexagonal Architecture (7 ports) │
│ └───────────────────┬──────────────────────┘
│ │
│ HTTP Bridge (internal)
│ ~5─12ms overhead · <1% of total
│ Atomic success/failure · Retry logic
│ │
│ ┌───────────────────▼──────────────────────┐
│ │ Python (FastAPI + LangGraph) │
│ │ Agent Orchestration · Tools │
│ │ LLM Calls · AI/ML Ecosystem │
│ └───────────────────┬──────────────────────┘
│ │
│ ┌───────────────────▼──────────────────────┐
│ │ LLM │
└──────────────└──────────────────────────────────────────┘
SSE · JSON
Markdown · Text
Node.js owns the edges. HTTP routing, content negotiation across MIME types (text/event-stream, application/json, text/markdown, text/plain), session management, and a hexagonal architecture with 7 port interfaces and 14+ adapters — all with compile-time safety. Clients request their preferred format via the Accept header; Node.js handles the rest.
Python owns the intelligence. LLM orchestration via LangGraph/LangChain, agent tool-calling loops, and the full AI/ML ecosystem — sitting cleanly behind an ILLMProvider port.
Key Hypotheses
This POC validates three claims before the team commits to production:
- Bridge pattern works — HTTP bridge adds ~5-12ms overhead (<1% of total request time) with clean failure semantics
- Best of both worlds — Neither language compromises; each handles what it was designed for
- Architecture is de-risked — TypeScript's hexagonal ports + Python's agent orchestration coexist without async safety issues
Architecture
Hexagonal Ports & Adapters
The TypeScript layer defines 7 port interfaces, each with swappable adapter implementations:
┌────────────────────────────────────────────────────────┐
│ Express HTTP │
│ Content Negotiation · SSE · JSON · MD │
├────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────┐ │
│ │ ILLMProvider │ │ IDatabase │ │ ICache │ │
│ │ (→ Python) │ │ (PostgreSQL) │ │ (Redis) │ │
│ └────────────────┘ └────────────────┘ └────────────┘ │
│ │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────┐ │
│ │ IMessageQueue │ │ ISecrets │ │ IBlobStore │ │
│ │ (Redis Stream) │ │ │ │ │ │
│ └────────────────┘ └────────────────┘ └────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ IObservability │ │
│ │ (Pino + Sentry + New Relic) │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────┘
End-to-End Token Streaming
┌─ text/event-stream (SSE)
┌─────────┐ tokens ┌──────────────┐ HTTP ┌─────────┐ │
│ LLM │ ───────▶ │ Python Agent │ ──────▶ │ Node.js │─┼─ application/json
│ │ │ (LangGraph) │ bridge │(Express)│ │
└─────────┘ └──────────────┘ └─────────┘ ├─ text/markdown
│
└─ text/plain
Tokens flow from the LLM through the Python agent service, across the HTTP bridge, and into the Node.js layer — which negotiates the response format based on the client's Accept header. Correlation IDs are tracked at every hop.
Tech Stack
| Layer | Technology |
| Node.js Runtime | Node.js 20+ · TypeScript (strict) · Express · Zod |
| Python Runtime | Python 3.12+ · FastAPI · Uvicorn · Pydantic |
| AI / LLM | Anthropic SDK · LangChain · LangGraph |
| MCP | @modelcontextprotocol/sdk (TS) · FastMCP (Python) |
| Database | PostgreSQL · Redis (cache + streams + search) |
| Observability | Pino · structlog · Sentry · New Relic · LangFuse* |
| Infrastructure | Docker Compose · ECS Fargate* |
| Testing | TDD · Blue-Green testing · No UI required |
* Planned for Phase 2
Getting Started
Prerequisites
Quick Start
# Clone the repository
git clone https://github.com/psenger/python-nodejs-bridge-poc.git
cd python-nodejs-bridge-poc
# Copy environment config
cp .env.example .env
# Start all services (Node.js + Python + Redis + PostgreSQL)
docker-compose up
# Or run services individually:
# Terminal 1 — Infrastructure
docker-compose up -d redis postgres
# Terminal 2 — Node.js
cd services/node
npm install
npm run dev
# Terminal 3 — Python
cd services/python
pip install -r requirements.txt # or: poetry install
uvicorn src.main:app --reload --port 8001
# Run tests
cd services/node && npm test # Node.js layer
cd services/python && pytest # Python layerRoadmap
Phase 1: MVP
- Product architecture and technical design
- Node.js HTTP server with SSE streaming (
res.write()direct socket control) - Python agent service (FastAPI + Anthropic SDK + LangGraph)
- HTTP bridge with atomic failure semantics
- End-to-end token streaming (LLM → Python → Bridge → Node.js → SSE)
- Hexagonal architecture (7 ports, 14+ adapters)
- MCP tool handlers (Redis-backed data lookup, business logic)
- Structured logging with correlation ID propagation across bridge
- Docker Compose local dev environment
- TDD with Blue-Green testing
Phase 2: Production Hardening
- ECS Fargate deployment with health checks and auto-scaling
- Circuit breakers and load testing
- LangGraph multi-agent orchestration
- LangFuse tracing (token costs, prompt versioning, agent step traces)
- Multi-channel support (voice, third-party connectors)
Why Not Just Use [X]?
| Alternative | What You Lose |
|---|---|
| Node.js only | No LangGraph/LangChain, limited AI ecosystem, no LangFuse observability |
| Python only | Retrofitted asyncio risks silent event loop blocking under concurrent SSE + tool-calling; no compile-time port safety |
| Microservices (gRPC) | Operational overhead of service mesh, proto compilation pipeline, and deployment complexity for a POC |
| Monorepo monolith | Forced to pick one language; the core tradeoff remains |
Project Structure
python-nodejs-bridge-poc/
├── services/
│ ├── node/ # TypeScript/Express (HTTP, SSE, hex architecture)
│ │ ├── src/
│ │ │ ├── ports/ # 7 port interfaces
│ │ │ ├── adapters/ # Adapter implementations (14+)
│ │ │ ├── routes/ # Express route handlers
│ │ │ ├── services/ # Business logic
│ │ │ ├── middleware/ # Express middleware
│ │ │ ├── config/ # Configuration loading
│ │ │ ├── models/ # Domain models / DTOs
│ │ │ ├── repositories/ # Data access
│ │ │ ├── utils/ # Utilities
│ │ │ └── types/ # TypeScript type definitions
│ │ └── tests/ # Unit, integration, e2e
│ └── python/ # FastAPI/LangGraph (AI orchestration)
│ ├── src/
│ │ ├── agents/ # LangGraph agent definitions
│ │ ├── tools/ # MCP tool implementations
│ │ ├── prompts/ # Prompt templates (versioned)
│ │ ├── api/v1/ # FastAPI route handlers
│ │ ├── services/ # Business logic
│ │ ├── schemas/ # Pydantic request/response schemas
│ │ ├── utils/ # Utilities
│ │ └── core/ # Core abstractions
│ └── tests/ # Unit, integration, fixtures
├── shared/
│ ├── api/openapi/ # Bridge contract (OpenAPI specs)
│ ├── configs/ # Shared configuration
│ └── scripts/ # Cross-service scripts
├── deploy/
│ ├── docker/ # Dockerfiles, compose config
│ ├── ci/ # CI/CD pipelines
│ └── scripts/ # Deployment scripts
├── docs/
│ ├── architecture/ # Architecture Decision Records
│ ├── api/ # API documentation
│ └── runbooks/ # Operational runbooks
├── agent-os/
│ ├── product/ # Mission, roadmap, tech stack
│ ├── specs/ # Feature specifications
│ └── standards/ # Coding standards (global, TS, Python, testing)
├── CLAUDE.md # Claude Code project instructions
├── .env.example # Environment variable template
├── LICENSE # MIT
├── CONTRIBUTING.md # Contribution guidelines
└── README.md
License
This project is licensed under the MIT License — see the LICENSE file for details.
Built with the belief that the best architecture uses the best tool for each job.