How We Run Node.js in the Browser (No Backend)
How WebFiddle runs a serverless Node.js environment in the browser using Web Workers, polyfills and SharedArrayBuffer for backend power.
How We Run Node.js in the Browser Without a Backend#
Node.js is traditionally a server-side runtime. It relies on C++ bindings, the operating system's file system, and low-level networking APIs. So how does WebFiddle allow you to run Node.js code directly in your browser, securely and instantly?
The answer lies in a combination of Web Workers, Polyfills, and modern browser capabilities.
The Challenge#
Running user code on a server is expensive and risky. You need:
- Sandboxing: To prevent malicious code from destroying the server.
- Scaling: Docker containers take time/resources to spin up.
- Latency: Sending keystrokes to a server feels sluggish.
To solve this, we moved the runtime to the client.
The Architecture#
WebFiddle's Node.js engine isn't actually the full V8 Node.js runtime. Instead, it's a carefully crafted shim layer running inside a Web Worker.
1. The Web Worker Isolation
We run your code in a dedicated thread (node-worker.js). This ensures that even if you write an infinite loop (while(true) {}), the main UI thread remains responsive.
// src/pages/NodeRunner.tsx
const worker = new Worker('/node-worker.js');
worker.postMessage({ type: 'RUN', payload: { code } });
2. Polyfilling Node.js Globals
Browsers don't have process or Buffer. We inject these globals into the worker scope before your code runs.
The Process Shim:
We simulate the process object to match the Node.js API surface:
self.process = {
version: 'v18.16.0 (WebFiddle Shim)',
platform: 'browser',
env: { NODE_ENV: 'development' },
nextTick: (cb) => setTimeout(cb, 0),
// ...stdout/stderr hooks
};
The Buffer Shim:
We implement a lightweight Buffer class using standard Uint8Array.
class BufferShim {
static from(str) {
return new TextEncoder().encode(str);
}
toString() {
return new TextDecoder().decode(this.data);
}
}
3. Top-Level Await Support
One of the best features of modern Node.js is Top-Level Await. To support this in our playground, we wrap user code in an async IIFE (Immediately Invoked Function Expression).
// How the engine executes your code
const userFunction = new AsyncFunction(`
return (async () => {
${userCode}
})();
`);
This allows you to write:
const data = await fetch('/api/data');
console.log(data);
...without getting a syntax error.
Why This Matters#
By running locally:
- Zero Latency: Results appear instantly.
- Privacy: Your code never leaves your device (unless you share it).
- Cost: We don't pay for expensive cloud compute, keeping WebFiddle free.
Try It Yourself#
You can experience this architecture live in our Node.js Sandbox. notice how fast the "Engine Ready" status appears—that's the Web Worker booting up in milliseconds!
WebFiddle