Core APIs
What are Node’s Core APIs?
Section titled “What are Node’s Core APIs?”Node.js ships with a rich set of built-in globals and modules that are available without installing anything. These are the primitives every Node.js program is built on.
Key globals and modules covered in this module:
process— access to the running process: environment variables, command-line arguments, the working directory, the platform, and lifecycle control.Buffer— a fixed-length chunk of raw binary data. Predates typed arrays and remains the standard way to work with bytes in Node streams and networking.node:fs/node:fs/promises— read, write, and navigate the filesystem. The promises API lets you useasync/awaitnatively.EventEmitter— the backbone of Node’s event-driven architecture. Streams, the HTTP server, and many third-party libraries all extendEventEmitter.
What this module covers
Section titled “What this module covers”| Lesson | Topic |
|---|---|
| process & env | process.argv, process.env, exit codes, and platform detection |
| Buffer | Binary data, encodings, and byte-level manipulation |
| File System | fs callbacks, sync APIs, and the fs/promises interface |
| EventEmitter | Custom events, listeners, and the once / removeListener lifecycle |
Newer built-ins worth knowing
Section titled “Newer built-ins worth knowing”Node keeps absorbing popular packages into the runtime:
node:sqlite— an embedded SQLite database (unflagged since 22.13, still Experimental on Node 24 — importing it prints anExperimentalWarning). Zero dependencies for local state, tests, and CLIs.fs.glob(22+) — file matching without theglobpackage.node:test— the test runner covered in the last module.
import { DatabaseSync } from 'node:sqlite';
const db = new DatabaseSync(':memory:');db.exec('CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT) STRICT');
const insert = db.prepare('INSERT INTO notes (body) VALUES (?)');insert.run('first');insert.run('second');
console.log(db.prepare('SELECT * FROM notes ORDER BY id').all());// [ { id: 1, body: 'first' }, { id: 2, body: 'second' } ]A first globals demo
Section titled “A first globals demo”The snippet below reads two globals that are available in every Node.js process without any require or import:
Output (verified on Node 24)
Node version: v24.13.0
String: Hello, Node!
Chars: 12
Bytes (UTF-8): 12Mental Model
Section titled “Mental Model”Two of these are true globals reachable from anywhere with zero import; the other two are modules you must pull in explicitly — and one of those modules (EventEmitter) is the base class the other three quietly depend on.
flowchart TB
subgraph Globals["always available, no import"]
process["process"]
Buffer["Buffer"]
end
subgraph Modules["explicit import"]
fs["node:fs / node:fs/promises"]
events["node:events — EventEmitter"]
end
events -->|"extended by"| fsStream["fs.ReadStream / fs.WriteStream"]
fs -->|"returns"| fsStream
events -->|"extended by"| httpNet["http.Server, net.Socket (later modules)"]
process -->|"reports on"| httpNet Trade-offs
Section titled “Trade-offs”| Choice | Benefit | Cost |
|---|---|---|
process / Buffer as globals | Zero-friction access from any file, no import churn | Harder to see a dependency at a glance — a function using process.env gives no import-level hint that it reads config |
Optional node: prefix on legacy modules (fs, path) | Old code (require('fs')) keeps working forever | Two spellings of the same module coexist in a codebase — inconsistent style |
Mandatory node: prefix on new modules (node:test, node:sqlite) | Impossible to shadow with an npm package named test or sqlite | No unprefixed fallback — copy-pasting old-style require('sqlite') fails with MODULE_NOT_FOUND instead of a clearer error |
Common Misconceptions
Section titled “Common Misconceptions”- “Every Node built-in needs an import” — fact:
processandBufferare globals; only modules likenode:fsandnode:eventsneedimport/require. - “The
node:prefix is just decoration” — fact: for legacy modules it’s optional style; fornode:testandnode:sqliteit is the only valid specifier — there is no barerequire('test'). - “
node:sqliteis production-ready because it ships in Node 24” — fact: it still prints anExperimentalWarningon every import on 24.13; the API can change in a semver-minor release.
Reading AI-generated code
Section titled “Reading AI-generated code”- Does generated code
require('sqlite')orrequire('test')without thenode:prefix? Both throwMODULE_NOT_FOUND— these two modules have no unprefixed form, unlikefsorpath. - Does a snippet treat
node:sqliteas stable (no warning handling, used casually in examples) when it is still Experimental on the target Node version? - Does the code import
Bufferorprocessfrom somewhere (import { Buffer } from 'node:buffer')? That’s valid but unnecessary — double-check it isn’t hiding a typo likebuferthat a linter would have caught on the global form.
🐛 Spot the bug
const { test } = require('test');
test('adds numbers', () => {console.log('ok');});Show the answer
node:test has no unprefixed form — only require('node:test') (or require('test') under --experimental-require-module… no, that flag doesn’t exist either) resolves. Plain require('test') throws Error [ERR_MODULE_NOT_FOUND] / MODULE_NOT_FOUND: Cannot find module 'test', because Node does not register bare test as a module specifier — only node:test. The fix is require('node:test') (or import { test } from 'node:test'). This is the same rule as node:sqlite: brand-new built-ins only exist under the prefixed name, unlike legacy modules such as fs where both spellings work.