In the fast-paced world of web development, choosing the right tool for the job often boils down to a delicate balance between flexibility and raw performance. Today, we’re diving deep into an intriguing head-to-head battle: Node.js versus Go, specifically in the context of HTTP proxying for injecting chaos. Our focus? The original chaos-proxy built with Node.js and its newer, Go-based counterpart, chaos-proxy-go.
The original chaos-proxy, a Node.js creation, initially shone as a versatile tool for testing TypeScript and JavaScript applications. Its event-driven architecture and the vast npm ecosystem made it a natural fit for rapid development, especially appealing to frontend and full-stack teams who valued custom middleware written in JS/TS. It’s truly a testament to Node.js’s adaptability!
However, the core concept of a chaos proxy—injecting latency, failures, or transforming headers into API traffic—is inherently language-agnostic. This realization paved the way for a challenger: a Go implementation aiming for superior performance and concurrency. While chaos-proxy-go sacrifices the custom JS/TS middleware capabilities, it promises to deliver the same core chaos features with a significant boost in speed. But does it truly deliver?
To answer this, an extensive benchmark was conducted, pitting the Node.js/Express-based chaos-proxy against chaos-proxy-go. The test setup was meticulously crafted using a Caddy server as the backend and the hey tool for load testing, ensuring a controlled environment and reproducible results. Let’s break down how this performance showdown unfolded.
The Battleground: System & Test Environment
To guarantee a fair fight, all benchmarks were executed locally on a single machine, minimizing external variables. Here’s a look at the foundational elements:
System Specifications:
- CPU: AMD Ryzen 7 5800H with Radeon Graphics
- Cores: 8 physical cores (16 logical processors with SMT enabled)
- Base Clock: 3.2 GHz
- RAM: 16GB DDR4
- Operating System: Windows 10 Home 22H2 64-bit
Test Setup Essentials:
- All tests were performed on
localhostto eliminate network latency variations. - A Caddy server, renowned for its speed, served as the backend API on port 8080.
- Both chaos-proxy (Node.js) and chaos-proxy-go were configured to forward requests to this Caddy backend.
- Minimal background activity ensured dedicated resources for the benchmarks.
- Each scenario was run multiple times, and the best/average results were reported for accuracy.
Software Versions in Play:
- Node.js: v24.2.0
- Go: go1.25.1 windows/amd64
- Caddy: v2.10.2 h1:g/gTYjGMD0dec+UgMw8SnfmJ3I9+M2TdvoRL/Ovu6U8=
- hey: v0.1.4
- chaos-proxy: v2.0.0 (latest at 11/10/2025)
- chaos-proxy-go: v0.0.5 (latest at 11/10/2025)
Backend Excellence: Caddy Server Configuration
The choice of Caddy as the backend was a strategic one. Its modern architecture, speed, and ease of configuration make it an ideal, low-overhead server, ensuring that any performance bottlenecks observed were solely attributable to the proxy layers.
The Caddyfile configuration was straightforward, setting up a simple JSON API endpoint at /api/hello and configuring CORS headers:
# Caddyfile
http://localhost:8080 {
# Add CORS headers for all responses
header {
Access-Control-Allow-Origin *
Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Access-Control-Allow-Headers "Content-Type, Authorization"
Content-Type application/json
}
# Simple JSON API endpoint
route /api/hello {
respond `{
"message": "Hello, World!",
"server": "Caddy",
"timestamp": "2025-10-03T22:00:00Z"}
` 200
}
# Log all requests
log {
output file access.log
format json
}
}
This setup confirmed that the backend wouldn’t be the limiting factor, allowing a clear assessment of the proxies’ performance overhead.
The Contenders: Proxy Setups
Three distinct scenarios were benchmarked to measure the performance impact:
1. Direct to Caddy (The Unproxied Baseline)
Requests were sent straight to the Caddy backend at http://localhost:8080/api/hello. This provides our gold standard—the absolute fastest possible response time without any intermediary, setting a crucial baseline.
2. chaos-proxy (Node.js/Koa)
The original Node.js/Express-based HTTP proxy, configured to listen on http://localhost:5000 and forward requests to the Caddy backend at http://localhost:8080. Importantly, no custom JS/TS middleware was enabled, isolating the raw proxying performance.
Its minimal chaos.yaml configuration was simply:
target: http://localhost:8080
port: 5000
3. chaos-proxy-go (Go)
The Go reimplementation, also listening on http://localhost:5000 and forwarding to the same Caddy backend. Like its Node.js sibling, it operated without additional middleware, focusing solely on core proxying capabilities. This version was run directly from its compiled Go binary.
It’s worth noting that both proxies used identical minimal configurations, and the focus was purely on raw proxy performance, not the speed of their chaos injection features.
The Methodology: Fair Play in Benchmarking
Consistency was key. The same benchmarking tool, request pattern, and methodology were employed across all scenarios. Each backend was restarted between tests to ensure no lingering caches skewed the results.
The ‘hey’ Test Command:
For each scenario, the command executed was: hey -n 1000 -c 50 http://localhost:
-n 1000: Total number of requests per test run.-c 50: Number of concurrent clients (connections).: 8080 for direct Caddy, and 5000 for both proxy implementations.
The beauty of this approach is its reproducibility; with the detailed specs and commands, anyone with a similar setup should be able to verify these findings.
The Verdict: Analyzing the Performance Data
Now, let’s look at what the numbers reveal. The results unequivocally showcase the performance characteristics of each setup:
| Scenario | Requests/sec | Avg Latency (s) | 99th %ile Latency (s) | Fastest (s) | Slowest (s) | Errors |
|---|---|---|---|---|---|---|
| Direct to Caddy | 28,384 | 0.0016 | 0.0116 | 0.0001 | 0.0121 | 0 |
| chaos-proxy (Node.js) | 4,262 | 0.0115 | 0.0417 | 0.0049 | 0.0430 | 0 |
| chaos-proxy-go (Go) | 8,828 | 0.0053 | 0.0140 | 0.0222 | 0.0004 | 0 |
Key Observations & Expert Insights:
The Baseline Dominance: As anticipated, direct communication with Caddy delivered stellar performance, clocking in nearly 28,400 requests per second with an average latency of a mere 1.6 milliseconds. This is our benchmark for efficiency, a testament to Caddy’s lean operation.
Node.js’s Performance Trade-off: The
chaos-proxybuilt with Node.js, while offering unmatched flexibility for JS/TS developers, introduced a significant performance penalty. Throughput plummeted by approximately 85% compared to the baseline, dropping to just 4,262 requests/sec. Average latency soared by over seven times to 11.5 milliseconds. What’s more concerning is the 99th percentile latency hitting 41.7 milliseconds, suggesting that under load, a noticeable number of requests experienced considerable delays. This is a classic example of how the overhead of a dynamic runtime and event loop management can impact raw speed, especially for I/O-bound proxy operations.Go’s Concurrency Prowess:
chaos-proxy-gotruly shined in comparison. It achieved more than double the throughput of its Node.js counterpart, processing 8,828 requests/sec, and halved the average latency to 5.3 milliseconds. Even its 99th percentile latency of 14.0 milliseconds was significantly better than Node.js, indicating a much more consistent and predictable performance profile. This stellar showing underscores Go’s strengths in concurrency and its compiled nature, which provides lower-level control and less runtime overhead, making it exceptionally well-suited for high-performance network services like proxies.
Conclusion: The Right Tool for the Right Job
This benchmark provides compelling evidence of Go’s performance advantages when it comes to an HTTP proxy, especially in scenarios where raw throughput and low latency are paramount. While the original chaos-proxy in Node.js offers unparalleled extensibility for teams deeply invested in the JavaScript/TypeScript ecosystem, it comes with a non-trivial performance cost under pressure.
For those prioritizing maximum performance and who can forego custom JavaScript/TypeScript middleware, chaos-proxy-go emerges as the clear frontrunner. Its efficiency allows for more resilient chaos engineering setups that impose less overhead on the system being tested. However, if your team’s workflow heavily relies on JS/TS middleware for complex chaos scenarios, the original chaos-proxy still stands as a highly flexible option, provided you’re aware of the performance implications.
Ultimately, the choice between Node.js and Go for a tool like a chaos proxy isn’t about one being “better” overall, but rather about aligning with specific project needs and priorities. It’s a classic engineering trade-off! What are your thoughts on balancing performance and ecosystem flexibility in development? We’d love to hear your experiences in the comments below!




