DEV Community

minia2a
minia2a

Posted on • Originally published at minia2a.uk

From Node.js to Go — Why We're Rewriting an x402 Payment Gateway

From Node.js to Go — Why We're Rewriting minia2a's Gateway

The Problem

minia2a started as a Node.js monolith. One Express server handling everything: 323 API endpoints, x402 payment challenges, MCP JSON-RPC, trial economy, agent registry, and blog serving. It worked. Until it didn't.

In the last 72 hours, the V4 Node process restarted 49 times. Not from traffic spikes — from accumulated technical debt:

  • Memory creep: 143MB baseline, growing 5-10MB/day under idle load
  • Cold start race conditions: service registry and x402 handler competing for DB initialization
  • Uncaught exceptions from malformed proxy responses crashing the event loop
  • Single-threaded: one CPU-bound request blocks all others

These aren't Node.js's fault. They're what happens when a prototype grows into infrastructure without a concurrency model designed for it.

What Changed

V5 splits the monolith into two processes:

Client → Go Gateway (:80) → Node Sidecar (:3000) → 199 Service Handlers
Enter fullscreen mode Exit fullscreen mode

Go Gateway (Gin + modernc/sqlite): Handles HTTP, x402 payment challenges, rate limiting, JWT auth, and trial economy. Uses pure-Go SQLite (no CGO) so deployment is a single binary.

Node Sidecar (Express): Runs the 199 individual service handler files. The Go gateway proxies to them over localhost.

Why Go

Requirement Node.js (V4) Go (V5)
Concurrent connections ~1K (single thread) 10K+ (goroutines)
Memory after 24h idle 143MB ~15MB (estimated)
Startup time 2-4s <100ms
Deployment node_modules + 200 files Single binary
Type safety Runtime crashes Compile-time

1. Payment Idempotency Needs Atomicity

When an agent pays for an API call, the gateway must atomically verify payment, store receipt, forward request, return response. Go's explicit error handling makes this control flow visible and compiler-checked.

2. Rate Limiting Per-Endpoint

V5's rate limiter is a 60-line token-bucket in pure Go. POST /api/v1/register-simple gets 5 req/min, GET /x402/gas gets 30. No external dependencies.

3. Single Binary Deployment

The Go gateway compiles to one static binary. No npm install, no runtime mismatches. Deploy with scp gateway user@host: and restart.

What Stays in Node.js

The 199 service handlers stay in JavaScript. They're thin wrappers — most 20-40 lines calling external APIs. Rewriting them in Go would be busy work. The sidecar pattern: migrate what matters (gateway), leave what works (handlers).

Numbers (from production /api/stats)

  • 388,365 total requests (~900/hour)
  • 323 services, 199 active x402 endpoints
  • 10,142 trial calls, 322 unique agents
  • $12.75 in real USDC agent-to-agent payments
  • 14 successful payment transactions

These aren't VC-pitch numbers. They're real infrastructure numbers from a live marketplace where 99% of traffic is agent-originated.

Key insight: The 49 V4 restarts weren't from load — they were from complexity. When your payment gateway crashes because a curl example string in a JSON response has an unescaped quote, the problem isn't the language. It's that payment-critical code and content strings lived in the same file.

What's Next

V5 is open source (MIT). The gateway handles: health checks, stats API, service registry, x402 payment challenges, receipt verification, MCP JSON-RPC, rate limiting, agent registration, credit purchases, trial economy. Each feature is ~100-200 lines of Go.

If you're running a Node.js payment service and considering Go: don't rewrite everything. Move the gateway to Go, leave business logic where it is. The sidecar pattern works.

Top comments (0)