Academy
ProgrammingJS AdvancedRuntime Environments

Runtime Environments

One Language, Many Worlds

JavaScript was born in the browser in 1995. For fifteen years that was its only home. Then Node.js arrived in 2009 and moved JavaScript to the server. Today there are four major runtimes — browser, Node.js, Deno, and Bun — each running the same language but exposing different globals, different APIs, and different module systems.

Code that assumes it's in a browser (document.getElementById) crashes in Node. Code that assumes Node (require, process.env) crashes in the browser. Understanding what each environment provides — and how to detect which one you're in — is foundational knowledge for any JavaScript developer.

The Browser Environment

The browser is where JavaScript started. It provides globals for interacting with the page, the network, storage, and the device:

What the Browser Does NOT Have

The Node.js Environment

Node.js took V8 out of Chrome and wrapped it with APIs for the server: file system, network sockets, processes, streams. It has dominated server-side JavaScript since 2009.

Key things Node provides that browsers don't:

// File system — read and write files
const fs = require("fs");
const path = require("path");

const content = fs.readFileSync("data.json", "utf8");
const parsed = JSON.parse(content);
fs.writeFileSync("output.json", JSON.stringify(parsed, null, 2));

// Environment variables — configuration without hardcoding
const port = process.env.PORT || 3000;
const dbUrl = process.env.DATABASE_URL;
const isProd = process.env.NODE_ENV === "production";

// Process information
console.log(process.pid); // process ID
console.log(process.platform); // "linux", "darwin", "win32"
console.log(process.memoryUsage().heapUsed); // bytes

// Streams — process data without loading it all into memory
const { createReadStream, createWriteStream } = require("fs");
createReadStream("large-file.csv")
  .pipe(csvParser())
  .pipe(createWriteStream("output.json"));

// Child processes — run other programs
const { exec } = require("child_process");
exec("git log --oneline -5", (err, stdout) => console.log(stdout));

CommonJS vs ES Modules — The Module System Split

Node.js originally used require() (CommonJS). Browsers and modern Node.js use import/export (ES Modules). This split is still a source of confusion:

// CommonJS (Node.js original — .js files without "type":"module" in package.json)
const fs      = require("fs");           // built-in
const express = require("express");      // npm package
const utils   = require("./utils.js");  // local file

module.exports = { myFunction };         // export
module.exports.myOtherFunction = ...;   // named export

// ES Modules (browsers, Deno, Bun, modern Node with .mjs or "type":"module")

export function myFunction() { ... }    // named export
export default class MyClass { ... }   // default export

Deno — The Secure-by-Default Runtime

Deno was created by Ryan Dahl — the same person who created Node.js — to fix Node's design mistakes. Released in 2020:

// Deno: TypeScript by default — no compilation step needed
// deno run script.ts

// No require() — ES Modules only, with full URLs

// Security model: NOTHING is permitted by default
// Must explicitly grant permissions at the CLI:
// deno run --allow-read --allow-net --allow-env script.ts

// File reading — same Web API style
const text = await Deno.readTextFile("./data.txt");

// Built-in test runner
Deno.test("addition works", () => {
  const result = 1 + 1;
  if (result !== 2) throw new Error("Math is broken");
});
// deno test

// Built-in formatter and linter
// deno fmt    — formats all files
// deno lint   — lints all files
// deno check  — type-checks without running

// Web-compatible APIs — fetch, WebSocket, crypto, URL all built in
const response = await fetch("https://api.example.com/data");
const data = await response.json();

Key differences from Node:

  • TypeScript natively — no tsc, no ts-node
  • Secure by default — no file/network/env access without explicit flags
  • URL imports — no node_modules, packages imported from URLs or deno.land
  • Built-in tooling — fmt, lint, test, check ship with the binary
  • Web-compatible APIs — closer to browser APIs than Node

Bun — The Speed-First Runtime

Bun launched in 2022 with one primary goal: be faster than Node at everything. It uses JavaScriptCore (Safari's engine) instead of V8:

// Bun: compatible with most Node.js code — drop-in replacement for many use cases

// File I/O — faster than Node's fs
const file = Bun.file("./data.json");
const content = await file.json();
await Bun.write("./output.json", JSON.stringify(content, null, 2));

// Built-in HTTP server — faster than Node's http module
Bun.serve({
  port: 3000,
  fetch(request) {
    return new Response("Hello from Bun!");
  },
});

// Built-in SQLite — no external dependency needed
const db = new Database("academy.db");
db.run(
  "CREATE TABLE IF NOT EXISTS students (id INTEGER, name TEXT, marks INTEGER)",
);

// Native TypeScript + JSX — no compilation needed, like Deno
// bun run script.ts

// Drop-in Node.js compatibility
// Most npm packages work — bun install is also faster than npm/pnpm

Key differences from Node and Deno:

  • Speed — startup, HTTP throughput, and file I/O benchmarks consistently faster
  • Node compatible — most Node.js code and npm packages work unchanged
  • Built-in bundler — replaces Webpack, Vite, esbuild for many use cases
  • Built-in SQLite, test runner, and package manager — single binary
  • JavaScriptCore — different engine from V8; occasionally different performance characteristics on specific workloads

Environment Detection — Writing Code That Runs Anywhere

Libraries and framework utilities often need to work in multiple environments. The pattern for safe detection:

The Web Platform API Convergence

The most important trend: Node, Deno, and Bun are all converging on browser-compatible Web APIs. Writing to these APIs means your code works everywhere:

Choosing a Runtime

What You Have Learned

JavaScript is not one environment. It is one language running in four distinct worlds, each with different capabilities, APIs, and design philosophies.

The key ideas:

  • Browser: DOM, Web APIs, localStorage — no file system, no process, no secrets
  • Node.js: file system, child processes, huge npm ecosystem — CommonJS legacy, security opt-out
  • Deno: TypeScript native, secure by default, URL imports — smaller ecosystem, different module system
  • Bun: fastest runtime, Node-compatible, built-in SQLite and bundler — youngest, still maturing
  • Web Platform APIs (fetch, URL, crypto, TextEncoder, AbortController) are the convergence point — write to these and your code works in all four runtimes
  • CommonJS vs ESM: require() is synchronous and dynamic; import is static and enables tree-shaking — modern projects use ESM
  • Environment detection: check for process, Deno, Bun, window — never assume, always detect
  • For your Astro site: you write browser-targeted code in components, Node-targeted code in API routes — Astro handles the boundary

In the next lesson: Data Structures from Scratch — linked lists, stacks, queues, trees, graphs, and hash maps. Not as academic exercises but as tools: understanding why Map is O(1) and why Array.includes is O(n) changes how you write code at scale.