Kill the Server: Why Holepunch Threw Away Node.js and Built 'Bare' The Holepunch team built a new JavaScript runtime called Bare by stripping down Node.js to its core, removing the standard library and HTTP assumptions to create a minimalist runtime optimized for peer-to-peer applications. Bare abstracts the JavaScript engine behind a C-API wrapper called libjs, allowing developers to swap engines like V8, QuickJS, or JerryScript for different deployment scenarios. The runtime provides only a module system, native addon system, and lightweight threads, leaving networking and other features to userland modules. When the Holepunch team set out to build Pear—a decentralized, peer-to-peer application runtime—they started with the obvious choice: Node.js. Node is the undisputed king of JavaScript outside the browser. It has a massive ecosystem, a battle-tested asynchronous event loop libuv , and the raw execution speed of Google's V8 engine. It seemed like the perfect foundation for a P2P stack. But as they dug deeper into cross-platform routing, NAT traversal, and mobile embedding, they hit a fundamental architectural roadblock: Node.js makes too many assumptions, and the biggest assumption it makes is that you are running a server. Node was built for the data center. It carries decades of legacy APIs http , net , tls that are tightly coupled to centralized, client-server web architecture. When you want to build a purely peer-to-peer, serverless network where devices connect directly via a Distributed Hash Table DHT , all of that built-in Node bloat becomes dead weight. So, they did what any obsessive systems engineering team does. They stripped Node down to its studs, threw away the bloated standard library, and built Bare . Here is a technical look at the Bare runtime, and why throwing away Node’s HTTP assumptions is the key to true P2P applications. At its core, Bare is a minimalist JavaScript runtime designed specifically for desktop, mobile, and IoT embedding. It keeps the best parts of Node's foundational architecture but aggressively decouples the JavaScript engine from the system APIs. Traditional Node.js Stack The Bare Runtime Stack ┌─────────────────────────┐ ┌─────────────────────────┐ │ User Application │ │ User Application │ ├─────────────────────────┤ ├─────────────────────────┤ │ Node Standard Library │ │ Userland Modules │ │ http, fs, net, crypto │ │ HyperDHT, Hyperdrive │ ├─────────────────────────┤ ├─────────────────────────┤ │ Node C++ Bindings │ │ Bare │ ├────────────┬────────────┤ ├────────────┬────────────┤ │ V8 │ libuv │ │ libjs │ libuv │ └────────────┴────────────┘ ├────────────┴────────────┤ │V8/ QuickJS / JerryScript│ └─────────────────────────┘ Notice the key difference at the bottom of the stack: libjs. Node is rigidly bound to Google’s V8 engine. Bare abstracts the JavaScript engine behind a C-API wrapper called libjs. This is a massive systems-level advantage. While Bare runs V8 by default for desktop performance, libjs allows developers to swap out the engine entirely. If you are deploying a P2P application to a highly constrained LTE router or a microcontroller, you can swap V8 for lightweight engines like QuickJS or JerryScript without changing the core runtime architecture. If you install Bare and try to spin up a quick web server, it will fail: js // This works in Node. It fails in Bare. const http = require 'http' ; // Error: Cannot find module 'http' Bare intentionally ships with almost nothing. There is no http. There is no net. There is no crypto. Why? Because in a true peer-to-peer architecture, standard HTTP is an anti-pattern. If you rely on http, you are relying on DNS routing, centralized Certificate Authorities TLS , and exposed public IP addresses. Instead of forcing a heavy standard library into the binary, Bare leaves feature implementation entirely to userland modules. The runtime provides only three core primitives: 1.A module system with bidirectional CJS and ESM interoperability . 2.A native addon system for linking low-level C/C++ libraries . 3.Lightweight threads with SharedArrayBuffer support . Everything else is imported a-la-carte. When you want to build a network connection in Bare, you don't use http. You use Holepunch's hyperdht, utilizing cryptographic keys instead of IPs. Node is notoriously difficult to embed cleanly into mobile applications. If you want to run a Node instance inside an iOS app, you end up wrestling with massive binaries, battery drain, and messy IPC Inter-Process Communication bridges. Because Bare shed the standard library, its memory footprint is drastically reduced. It treats mobile as a first-class citizen. Through Bare Kit, developers can spin up "worklets"—isolated Bare threads running directly inside native mobile frameworks SwiftUI for iOS, or Android Services . js // Spawning a Bare worklet for background P2P sync import { Worklet } from 'bare-kit' const syncThread = new Worklet '/hyperdrive-sync.js' syncThread.on 'message', msg = { console.log 'Mobile UI received state update from P2P swarm:', msg } syncThread.postMessage { command: 'START SYNC' } This allows a React Native or Swift frontend to offload all the heavy lifting—DHT routing, UDP hole punching, and data replication—to a highly efficient, native background thread that won't freeze the mobile UI. When you look at Bare, you realize that the Node.js architecture we’ve been using for 15 years has subtly brainwashed us. We assume that writing backend JavaScript inherently means writing server logic.Bare proves that JavaScript can be used for something far more resilient. By stripping away the bloated legacy of the Web2 data center, Holepunch has created a runtime that actually belongs on the edge. It forces you to stop thinking about endpoints and status codes, and start thinking about swarms, peers, and cryptographic tunnels. If you want to build systems that governments can't block and cloud providers can't crash, you have to kill the server. Bare is the runtime designed to do exactly that.