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, nots-node - Secure by default — no file/network/env access without explicit flags
- URL imports — no
node_modules, packages imported from URLs ordeno.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;importis 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.