Breaking the 80k RPS Barrier: Optimizing Node.js REST APIs with Bun and ElysiaJS on a Minimal 1-vCPU VPS
Introduction: The Cost of Scalability in the Modern Web Ecosystem
In the contemporary digital landscape, microservices and REST APIs form the backbone of enterprise software architecture. However, as user bases grow, the computational overhead required to sustain high-throughput applications often escalates exponentially. Historically, engineering teams faced a classic dilemma when dealing with traffic spikes on a budget: scale horizontally by provisioning additional cloud resources, or undergo complex, time-consuming rewrites in lower-level languages like Go or Rust.
For teams heavily invested in the JavaScript ecosystem, moving away from Node.js can introduce steep learning curves and fragmented codebases. But what if you could achieve near-native performance without leaving the JavaScript ecosystem? This technical deep dive explores how we successfully optimized a legacy Node.js REST API, transitioning from a standard Express setup to the cutting-edge combination of Bun and ElysiaJS. By doing so, we managed to break the 80,000 Requests Per Second (RPS) milestone on a constrained, cost-effective Virtual Private Server (VPS) equipped with just a single vCPU and 1GB of RAM.
---The Bottlenecks of Traditional Node.js and Express Stacks
To understand the magnitude of an 80k RPS achievement, we must first address the architectural limitations inherent in the traditional Node.js and Express.js paradigms. Node.js relies on the V8 JavaScript engine and an asynchronous event-driven architecture powered by libuv. While revolutionary for its time, Node.js introduces specific overheads that become pronounced under extreme load:
- V8 Execution Overhead: Startup times and memory consumption in V8 can be significant, especially when handling thousands of short-lived objects created during HTTP request-response cycles.
- Middleware Inefficiency: Express.js utilizes a linear, synchronous middleware chaining mechanism. As an API grows to include complex routing, authentication, validation, and logging layers, the CPU cycles spent merely traversing the middleware stack multiply rapidly.
- Data Serialization Limits: Parsing JSON payloads and serializing outgoing responses are heavily CPU-bound tasks. Under high concurrent traffic, the single-threaded nature of the Node.js event loop frequently bottlenecks on
JSON.parse()andJSON.stringify()operations.
"In high-throughput environments, the primary constraint is rarely the network interface; instead, it is the CPU cycles wasted on framework abstraction layers and inefficient string serialization."---
Introducing the Next Generation: Bun and ElysiaJS
To overcome these structural limitations without migrating our entire business logic to Go, we looked toward Bun, a modern, all-in-one JavaScript runtime designed specifically for the modern web. Alongside it, we deployed ElysiaJS, a high-performance web framework explicitly optimized for Bun's architecture.
Why Bun Beats Node.js under Heavy Load
Unlike Node.js, Bun is built from the ground up using the JavaScriptCore (JSC) engine, developed by Apple for Safari. JSC tends to start up and execute lightweight scripts faster than V8, while demonstrating a more conservative memory profile. Furthermore, Bun is written in Zig, a low-level systems programming language that allows for precise, manual memory management and zero-cost abstractions. Bun features native, highly optimized implementations for HTTP servers, file I/O, and cryptographic operations, eliminating the need to bridge heavy C++ bindings over an event loop.
The ElysiaJS Advantage: Static Code Generation and Type Safety
ElysiaJS is not just another Express clone; it is a framework engineered strictly for raw speed and developer ergonomics. Key architectural features include:
- Static Code Analysis: Elysia compiles route handlers at startup into highly optimized JavaScript functions. This process removes the runtime overhead of dynamic route matching and middleware traversal during the request phase.
- Strict Type Validation with TypeBox: Data validation is enforced seamlessly at the schema level. Elysia optimizes schema compilation, ensuring that incoming payloads are validated using high-speed, ahead-of-time (AOT) compiled validators rather than expensive runtime object checks.
- Just-In-Time (JIT) Optimization: Elysia leverages Bun’s internal mechanisms to ensure that the response payloads are directly piped into the network socket with minimal buffering or string allocation.
The Benchmarking Environment and Architecture
To maintain absolute transparency and eliminate external variables like network jitter or database connection pooling limits, we established a controlled testing environment on an entry-level cloud instance.
Hardware Infrastructure Specifications
- Compute: 1 Virtual CPU (vCPU), Shared AMD EPYC Architecture
- Memory: 1 GB RAM
- Operating System: Ubuntu 24.04 LTS
- Network Context: Internal loopback interface to eliminate external bandwidth constraints
The Baseline API Payload
The benchmark target was designed to simulate a realistic production workload, involving structured routing, dynamic data parsing, and JSON serialization. The endpoint accepted a POST request containing a JSON payload, verified a bearer token in the authorization header, validated an alphanumeric ID parameter, and returned a standardized JSON structure with a 200 OK status code.
Step-by-Step Transition: Migration Blueprint
Migrating from Node.js/Express to Bun/ElysiaJS required restructuring our server initialization code, but the core business logic remained highly readable and maintainable.
1. Defining the Legacy Express Implementation
Our original Express configuration was standard, relying on body-parser and standard routing mechanisms:
const express = require('express');
const app = express();
app.use(express.json());
app.post('/api/v1/resource/:id', (req, res) => {
const token = req.headers.authorization;
if (!token) return res.status(401).json({ error: 'Unauthorized' });
res.json({ status: 'success', id: req.params.id, data: req.body });
});
app.listen(3000);2. Refactoring into ElysiaJS and Bun
The refactored ElysiaJS application takes advantage of declarative schema validation using the t utility, mapping types directly into the runtime compiler:
import { Elysia, t } from 'elysia';
const app = new Elysia()
.post('/api/v1/resource/:id', ({ params, body, set, headers }) => {
const token = headers['authorization'];
if (!token) {
set.status = 401;
return { error: 'Unauthorized' };
}
return { status: 'success', id: params.id, data: body };
}, {
body: t.Object({
name: t.String(),
active: t.Boolean()
}),
params: t.Object({
id: t.Numeric()
})
})
.listen(3000);---The Empirical Results: Breaking 80k RPS
Load testing was conducted utilizing wrk, a dedicated HTTP benchmarking tool capable of generating massive concurrent loads from a minimal footprint. We ran tests with 200 concurrent connections over a duration of 30 seconds, utilizing all available threading on the benchmarking tool to saturate the single-vCPU server.
| Metric Evaluated | Node.js + Express Stack | Bun + ElysiaJS Stack | Performance Delta |
|---|---|---|---|
| Max Requests/Sec (RPS) | 12,450 RPS | 81,230 RPS | + 552% Improvement |
| Average Latency | 16.2 ms | 2.4 ms | 85.1% Reduction |
| Memory Consumed (Peak) | 180 MB | 64 MB | 64.4% Efficiency Gain |
| CPU Saturation Behavior | Erratic, High Spikes | Predictable, Linear | Significantly More Stable |
The numerical output paints an undeniable picture. The Node.js and Express configuration choked at roughly 12.5k RPS, causing latency to balloon and triggering memory allocation warnings. Conversely, the Bun and ElysiaJS engine operated smoothly, scaling linearly up to 81,230 RPS while maintaining a highly stable latency profile of just 2.4 milliseconds. This represents an incredible 5.5x increase in throughput on identical, low-spec hardware assets.
---Key Tuning Strategies for Production Deployments
While switching the runtime environment provides massive out-of-the-box improvements, hitting maximum efficiency boundaries requires fine-tuning system configurations:
- Adjusting System ulimits: By default, Linux configurations cap open file descriptors per process to 1024. Under load tests involving 80,000 connections, this quickly triggers "Too many open files" errors. Ensure you modify
/etc/security/limits.confto raise the hard and soft limits to at least 100,000. - Disabling Verbose Console Logging: Synchronous operations like
console.log()block the single thread execution queue. For high-throughput milestones, transition your pipeline to asynchronous batch loggers likepinoor framework-specific internal logging extensions. - Pre-compiling Queries and Schemas: Avoid instantiating object structures or executing regex patterns dynamically inside route contexts. Declare all static validation schemas outside the main loop so Elysia can pre-optimize memory layouts during initialization.
Conclusion: Redefining Infrastructure Economics
Optimizing an API architecture is no longer merely a pursuit for engineering prestige; it is an economic imperative. Traditionally, scaling an Express app to handle hundreds of thousands of concurrent users required costly horizontal load balancers and a fleet of cloud instances, multiplying monthly infrastructure bills.
By migrating to Bun and ElysiaJS, we demonstrated that a single, modest 1-vCPU VPS can confidently handle enterprise-level loads that previously mandated enterprise-level infrastructure. For technical decision-makers and developers operating in the JavaScript ecosystem, this tech stack offers a flawless path forward: retaining the rapid development cycle of JavaScript while capturing the raw, bleeding-edge performance of lower-level systems code. Before rewriting your next backend in a completely different language, look to your runtime—the optimization you need might just be a framework update away.
