NodeJSNode.js vs Browser JavaScript

Node.js vs Browser JavaScript

Node.js and the browser both run JavaScript, but they are different host environments with different global objects, APIs, security models, and assumptions. The language is identical; the surroundings are not. Understanding precisely where they differ eliminates a whole class of "why is document undefined?" and "why can't I read this file?" confusion — and tells you which code can be shared between front end and back end.

Same language, different host

Both run an engine (the browser and Node.js both use V8 in Chrome's case). The engine implements ECMAScript: variables, functions, classes, objects, Math, JSON, promises, async/await. Everything beyond the pure language is supplied by the host — and that is where they diverge.

Capability

Browser

Node.js

Global object

window / self

global / globalThis

The DOM (document)

Yes

No — there is no page

File system access

No (sandboxed)

Yes (fs module)

Raw network sockets

No

Yes (net, dgram, http)

Make HTTP requests

fetch, XMLHttpRequest

fetch, http/https

Module system

ES Modules only

CommonJS and ES Modules

Environment variables

No

process.env

Spawn OS processes

No

child_process

Multithreading

Web Workers

Worker Threads, cluster, child processes

Binary data

Blob, typed arrays

Buffer, typed arrays

Persistent storage

localStorage, IndexedDB, cookies

Files, databases

Security boundary

Same-origin policy, sandbox

Full OS access (as the user)

Primary concern

UI and user interaction

I/O, servers, tooling

What the browser has that Node lacks

There is no DOM in Node.js. document, window, alert, localStorage, navigator, and the entire UI surface do not exist — there is no web page for them to act on. Code that manipulates the page cannot run in Node unmodified.

Browser-only — these throw ReferenceError in Node

JS
document.querySelector('h1').textContent = 'Hi'
window.localStorage.setItem('token', 'abc')
window.addEventListener('resize', onResize)
Testing DOM code in Node
Tools like **jsdom** simulate a DOM inside Node so you can unit-test browser code without a real browser — which is exactly how test runners like Jest and Vitest provide a fake `document`.
What Node has that the browser lacks

Node-only capabilities

JS
import fs from 'node:fs'

// Read a file from disk — impossible in a sandboxed browser.
const config = fs.readFileSync('config.json', 'utf8')

// Inspect the process and environment.
console.log(process.env.NODE_ENV)   // 'production'
console.log(process.platform)       // 'linux'
console.log(__dirname)              // current directory (CommonJS)

// Open a TCP server — the browser can never do this.
import net from 'node:net'
net.createServer(socket => socket.end('hello\n')).listen(7000)
Globals compared

Use globalThis for code intended to run in both environments — it resolves to window in the browser and global in Node. Node additionally injects CommonJS-only names — __dirname, __filename, require, module, exports — which do not exist in ES modules. In ESM, derive the path values from import.meta.url instead:

ESM equivalent of __dirname

JS
import { fileURLToPath } from 'node:url'
import { dirname } from 'node:path'

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
The shrinking gap: shared Web Platform APIs

Modern Node.js has deliberately adopted many Web Platform APIs so that code is more portable and developers face fewer "this works in the browser but not Node" surprises. Available in recent Node versions:

  • fetch, Request, Response, Headers — the same HTTP client you use in the browser.

  • URL and URLSearchParams.

  • TextEncoder / TextDecoder.

  • structuredClone for deep cloning.

  • AbortController / AbortSignal for cancellation.

  • Web Streams (ReadableStream, WritableStream) alongside Node streams.

  • Web Crypto (crypto.subtle), performance, queueMicrotask, and timers.

Isomorphic / universal code

"Isomorphic" (or "universal") code runs unchanged on both the server and the client — the foundation of frameworks like Next.js and Remix. The rule of thumb:

  • Pure logic — validation rules, date math, formatting, business calculations, pricing — touches neither the DOM nor the file system, so it can be shared freely.

  • Environment-specific code — DOM access, fs, process — must be kept on the correct side, or hidden behind an abstraction that has a browser implementation and a Node implementation.

  • Bundlers (Vite, webpack, esbuild) and the package.json "exports" field with "browser"/"node" conditions let a library ship different code to each environment.

Guarding environment-specific code

JS
// A shared module that adapts at runtime
const isNode =
  typeof process !== 'undefined' && process.versions?.node != null

export function getStorage() {
  return isNode ? new FileStorage() : new LocalStorageAdapter()
}
Why this matters for you
When you copy a snippet from a browser tutorial into a Node script (or vice-versa), the first question to ask is: *does it touch a host API that only exists on one side?* If it uses `document`, it is browser-only; if it uses `fs` or `process`, it is Node-only.
Next
Curious what actually executes your JavaScript in both places? It is the same engine family — read [The V8 JavaScript Engine](/nodejs/v8-engine).