DEV Community

Vivian Voss
Vivian Voss

Posted on • Originally published at vivianvoss.net

JavaScript Backend: The Full-Stack Tax

Infographic comparing

The Invoice — Episode 16

"One language for frontend and backend! Share code! One team! No context-switching!"
Splendid. Let us examine what you are actually paying for.

In 1995, Brendan Eich built JavaScript in ten days. For form validation and image rollovers. Thirty years later, it runs production backends. The language has not changed. The expectations have.

The Resource Invoice

Express: 20,000 requests per second. Go: 40,000. Rust: 60,000. On Lambda, Node.js averages 20ms. Rust: 1.12ms. Eighteen times faster for the same work.

The real cost is memory. Express idles at 30-50 MB. Go: 5-10 MB. Rust: 1-2 MB. V8's garbage collector runs whether you need it or not. When it pauses, your latency spikes. You are paying for the runtime, not your application.

The Architecture Invoice

One thread. That is your entire backend. Like a server in 1983. Every CPU-intensive operation blocks every other request.

The fix is not fixing the runtime. The fix is containerisation. Cannot use multiple cores? Spin up multiple containers. Cannot manage containers? Add Kubernetes. The entire cloud-native orchestration stack exists, in part, to compensate for a language that does not know what to do with your lovely CPU cores.

Go uses all cores by default. Rust uses all cores by default. Node.js uses one, then asks for an orchestration platform. One does wonder how we ended up here.

The Type Safety Invoice

JavaScript has no types. The fix: TypeScript, a transpilation layer that adds a mandatory build step to a language designed to run directly. Type checking consumes 95% of compilation time. A 22,000-line project takes 13 seconds. Large codebases: three minutes per compile.

The workaround: bypass tsc with swc (96% faster) and lose type checking at build time. You have added a type system, then disabled it for performance. Marvellous.

The Security Invoice

npm hosts 3.2 million packages. In 2024, 500,000 were malicious: 98.5% of all malware across every language ecosystem. In September 2025, eighteen packages with 2.6 billion weekly downloads were compromised in one attack. debug. chalk. In your dependency tree right now.

The insecurity is not merely npm. It is the architecture. Node.js loads third-party code at runtime with full system access. Go compiles dependencies once. Rust links statically. The attack surface is fundamentally different.

The Alternative

For I/O-bound real-time workloads (chat, streaming, webhooks), Node.js is genuinely competent. That is what it was designed for.

For everything else: Rust gives you a single binary with no garbage collector, native concurrency, and static linking at 1-2 MB. Go gets you halfway there, though Google's GC still takes its cut. Neither needs a transpilation step or a package manager at runtime.

The Energy Invoice

Pereira et al. (2021) measured energy consumption across languages, normalised to C = 1.00:

Language Energy Memory
Rust 1.03x 1-2 MB idle
Go 3.23x 5-10 MB idle
JavaScript 4.45x 30-50 MB idle
PHP 29.30x 15-30 MB idle

At data centre scale (1 billion requests/day):

Language Energy/year Cost/year ($0.10/kWh) CO2/year
Rust ~45 MWh $4,500 19.6 t
Node.js ~195 MWh $19,500 85.0 t
PHP ~1,283 MWh $128,300 559.4 t

Your language choice is an infrastructure decision. And a climate decision.

Tenable rewrote one Node.js service in Rust: 700 CPUs saved, 300 GB RAM freed, 50% latency reduction.

The Pattern

Brendan Eich built JavaScript to validate forms. Ryan Dahl put it on the server, then called node_modules "an irreparable mistake" and built Deno to fix it. The language's own architect moved on. One does wonder whether the ecosystem noticed.

Read the full article on vivianvoss.net →


By Vivian Voss — System Architect & Software Developer. Follow me on LinkedIn for daily technical writing.

Top comments (0)