---
title: 'Differences from the Browser'
source: 'https://academia.sh/en/courses/nodejs/differences-from-the-browser'
course: 'The Node.js Runtime'
language: en
updated: '2026-08-17T18:09:52+00:00'
license: 'CC BY-SA 4.0'
---

# Differences from the Browser

The host environment concept, the global object's contents, the two module systems coexisting, and the server runtime's capability boundaries.

The Asynchronous JavaScript and the Runtime course established the single-threaded
execution model and the event loop. Everything covered there belonged not to the
language but to the environment hosting it: the task queue and the timers are not
defined in the language standard either. This course takes up the same language in a
different host environment.

The question: when the same syntax, value model, and event-loop concept run in a
server process instead of a browser, what changes? The answer falls under two
headings — which names are available, and what the process is authorized to do.

## The Language Core and the Host Environment

The language standard defines syntax and the value model: `Array`, `Map`, `Promise`,
`JSON`, `Math` belong to this layer. It says how these values behave, not how to
reach the outside world with them.

The **host environment** is the program running the language, and it provides the
contact with the outside world. A browser is a host environment: it offers the
document tree, cookies, screen events. A server runtime is a host environment too: it
offers the file system, process information, network listening.

The distinction can be tested directly. The file below prints whether a set of names
is present on the global object:

```js
// environment.mjs
const names = [
  'window', 'document', 'localStorage', 'alert',
  'process', 'Buffer', 'global',
  'fetch', 'URL', 'TextEncoder', 'AbortController', 'structuredClone',
];
for (const name of names) {
  console.log(`${name.padEnd(16)} ${name in globalThis ? 'yes' : 'no'}`);
}
```

```sh
node environment.mjs
```

```
window           no
document         no
localStorage     no
alert            no
process          yes
Buffer           yes
global           yes
fetch            yes
URL              yes
TextEncoder      yes
AbortController  yes
structuredClone  yes
```

The output shows three groups. The first exists only in the browser: the document
tree, page storage, and UI calls have no counterpart on the server. The second exists
only server-side: `process` represents process information, `Buffer` represents raw
bytes.

The third group is more interesting. `fetch`, `URL`, `TextEncoder`,
`AbortController`, and `structuredClone` are browser interfaces, but they belong to
the web platform, not the language. The server runtime implements a portion of these
under the same contract, so code running in both environments does not need two
separate versions of every interface. This overlap keeps growing: if an interface is
documented as present from a given version onward, it can be relied on; where it
might be missing, a presence check is used.

The name `global` is an older name pointing to the same object as `globalThis`.
Writing `globalThis` in new code makes the same expression work in both environments.

## Two Module Systems

The browser has a single module form. The server runtime carries two forms together
and picks one per file.

**CommonJS** loads synchronously through a `require` call and exports by writing to a
`module.exports` object. **ES modules** work through `import` and `export`
declarations and resolve dependencies before running them. The difference between the
two was detailed in the Modules, Tooling and the Ecosystem course; what matters here
is how the choice is made.

The choice looks at two rules: file extension and the nearest package manifest. A
`.cjs` file is always CommonJS, `.mjs` is always an ES module. For a `.js` extension,
the decision comes from the `type` field in the first `package.json` found walking up
from the file's directory: `module` means an ES module, `commonjs` or an absent field
means CommonJS.

The runtime difference between the two forms is measurable:

```js
// scope.cjs
console.log('this === module.exports :', this === module.exports);
console.log('__filename last part   :', __filename.split('/').pop());
console.log('typeof require         :', typeof require);
```

```js
// scope.mjs
console.log('this                    :', this);
console.log('import.meta.url last part:', import.meta.url.split('/').pop());
console.log('typeof require          :', typeof require);
```

```sh
node scope.cjs
```

```
this === module.exports : true
__filename last part   : scope.cjs
typeof require         : function
```

```sh
node scope.mjs
```

```
this                    : undefined
import.meta.url last part: scope.mjs
typeof require          : undefined
```

A CommonJS file is wrapped like a function body: `require`, `module`, `__filename`,
and `__dirname` are that wrapper's parameters, not globals. An ES module has none of
these; its own location is given through `import.meta.url`, not as a file path but as
an address. ES modules are used throughout the course; CommonJS appears only where the
difference needs to be seen.

## Capability and Authority

The real difference is not in the name list, it is in what the process can do.

Code running in a browser is treated as hostile toward the user's machine and kept in
a **sandbox**: it cannot read an arbitrary file, cannot listen on an arbitrary port,
and can only reach another origin to the extent that origin permits. These
restrictions are the guarantees a browser gives the user.

The server runtime has no such sandbox. The process runs with the authority of the
user who started it. The permission model from the Introduction to Linux course
applies here directly: whatever files the process can read, your script can read too.
The same holds for writing and deleting.

This authority shows in the short file below:

```js
// first-read.mjs
import { readFileSync } from 'node:fs';

const text = readFileSync('measurements.ndjson', 'utf8');
const lines = text.trim().split('\n');

console.log('line count:', lines.length);
console.log('first record:', JSON.parse(lines[0]));
```

The `node:` prefix declares that the name points to a **built-in module**. It
resolves without the prefix too; the prefixed spelling settles which one is meant if a
file or package under the same name exists. The course uses the prefixed form
throughout.

## The Course's Working File

One concrete piece of work will be built throughout this course: a small service that
collects records from remote measurement nodes, summarizes them, and serves them over
HTTP. The input is a text file where every line is an independent JSON object. Save
the twelve lines below under the name `measurements.ndjson`:

```
{"time":"2024-02-07T09:12:44Z","node":"edge-01","metric":"temperature","value":21.4}
{"time":"2024-02-07T09:12:44Z","node":"edge-01","metric":"humidity","value":48.0}
{"time":"2024-02-07T09:13:02Z","node":"edge-02","metric":"temperature","value":19.8}
{"time":"2024-02-07T09:13:02Z","node":"edge-02","metric":"humidity","value":52.5}
{"time":"2024-02-07T09:14:19Z","node":"edge-01","metric":"temperature","value":21.9}
{"time":"2024-02-07T09:14:19Z","node":"edge-03","metric":"temperature","value":24.1}
{"time":"2024-02-07T09:15:30Z","node":"edge-02","metric":"temperature","value":20.2}
{"time":"2024-02-07T09:15:30Z","node":"edge-03","metric":"humidity","value":41.3}
{"time":"2024-02-07T09:16:04Z","node":"edge-01","metric":"humidity","value":47.2}
{"time":"2024-02-07T09:16:04Z","node":"edge-03","metric":"temperature","value":24.6}
{"time":"2024-02-07T09:17:22Z","node":"edge-02","metric":"temperature","value":20.7}
{"time":"2024-02-07T09:17:22Z","node":"edge-01","metric":"temperature","value":22.3}
```

The four fields on each line carry, in order, the moment of measurement, the sending
node, the measured quantity, and the value. This one-record-per-line format is
compatible with the line-based filters from the Shell Programming course: it can be
processed line by line without loading the whole file into memory — decisive in the
Streams topic.

After putting the file in place, run the first read:

```sh
node first-read.mjs
```

```
line count: 12
first record: {
  time: '2024-02-07T09:12:44Z',
  node: 'edge-01',
  metric: 'temperature',
  value: 21.4
}
```

This shows work with no browser counterpart: a local file was read by name, without
user consent. The rest of the course builds on this capability — and takes up the
responsibility it brings.

The error that comes up when a file is not found is a form you will see often
throughout the course:

```sh
node -e "import('node:fs').then(m => m.readFileSync('missing.ndjson','utf8')).catch(e => console.log(e.code, '|', e.syscall, '|', e.path))"
```

```
ENOENT | open | missing.ndjson
```

`ENOENT` is the error name the operating system returns from the system call;
`syscall` says which call failed, `path` says for which path. These three fields let
you decide without reading the message text — the text can change, the code does not.

## Summary

- The language standard defines the value model; access to the outside world is the
  host environment's responsibility. The browser and the server runtime are two
  separate host environments.
- The global object has three groups: names found only in the browser, names found
  only on the server, and shared interfaces inherited from the web platform.
- Module form is chosen by file extension (`.cjs`, `.mjs`) or the `type` field in
  the nearest package manifest; a CommonJS file is wrapped and receives the names
  `require`, `module`, `__filename` as parameters.
- A server process has no sandbox; the process runs with the file and network
  authority of the user who started it.
- Writing built-in modules with the `node:` prefix removes the chance of a name
  collision; file system errors are distinguished by their `code`, `syscall`, and
  `path` fields.

## Next Step

In this lesson, the file was read with `readFileSync` — by making the process wait.
In a single-threaded server, that waiting is costly: no other request can be
processed during it. The next lesson opens up, phase by phase, the mechanism used to
avoid that cost — with measurable examples of the order the event loop drains its
queues, when timers run, and where input/output callbacks enter that order.
