Node.jsWebAssemblyArchitectureEngineering

    How We Run Node.js in the Browser (No Backend)

    Jan 18, 20266 min read

    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:

    1. Sandboxing: To prevent malicious code from destroying the server.
    2. Scaling: Docker containers take time/resources to spin up.
    3. 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!

    Run Node.js Code Now

    Ready to try it yourself?

    Experience the power of WebAssembly and Node.js directly in your browser. No setup required.