Node.js is a runtime for JavaScript outside the browser: it takes the same language you use to build interfaces and lets you run it on a server, in the terminal and on your own machine. Node.js powers APIs and backends, chats, command-line utilities and almost all modern frontend tooling, from npm to Vite. Understanding what Node is made of and how it differs from the browser will save you from many mistakes in the upcoming topics.
What Node.js is made of
Node.js is neither a language nor a framework but a runtime: a program that reads your .js file and executes it. Ryan Dahl created it in 2009 by combining Chrome's fast JavaScript engine with an asynchronous I/O library. Inside Node there are four main layers:
| Component | Language | What it does |
|---|---|---|
| V8 | C++ | Chrome's JavaScript engine: parses code, compiles it to machine code (JIT), manages memory and garbage collection |
| libuv | C | The event loop and asynchronous I/O: files, network, timers, a thread pool; works the same on Linux, macOS and Windows |
| C++ bindings | C++ | The "bridge" between JavaScript and system libraries: when you call fs.readFile, a binding passes the request to libuv |
| Standard library | JavaScript | Built-in modules node:fs, node:http, node:path, node:crypto, node:os and more |
On top of that, Node bundles OpenSSL (encryption and HTTPS), llhttp (HTTP parsing), undici (the fetch implementation), ICU (language and date support) and other libraries. The versions of all components live in the process.versions object; it is handy to inspect it with the node -p one-liner, covered in detail in the topic "The REPL and Running Scripts":
node -p "process.versions"{
node: '24.21.0',
acorn: '8.18.0',
...
openssl: '3.5.8',
undici: '7.29.1',
uv: '1.52.1',
v8: '13.6.233.17-node.53',
...
}Here you can see that Node 24.21 runs on V8 13.6 and libuv 1.52. The V8 version determines which new JavaScript features are available: if the engine supports a syntax, Node supports it too.
💡 Put simply: V8 is responsible for how to execute JavaScript, and libuv for how to wait for the disk and the network without stopping the program. We will see exactly how this works in the topic "The Node.js Event Loop".
Your first script
A script needs no HTML and no browser, just a file and a terminal. Create a file hello.js in any folder:
console.log('Hello from Node.js!');
console.log(2 + 3);
console.log(`Node version: ${process.version}`);
console.log(`Platform: ${process.platform}, architecture: ${process.arch}`);Run it with the node command followed by the file name (the terminal must be in the same folder):
node hello.jsHello from Node.js!
5
Node version: v24.21.0
Platform: linux, architecture: x64console.log prints text not to the DevTools console but straight to the terminal. process.version is the Node version, process.platform is the operating system (linux, darwin for macOS, win32 for Windows), and process.arch is the CPU architecture. Your values may differ. When the code in the file ends and there is nothing left to wait for, Node exits the process on its own.
What Node can do and the browser cannot
For security reasons the browser keeps a page in a "sandbox": it cannot freely read files on disk or find out the user's name. Node has no such restrictions: a script has the same rights as your user account. Save this example as files.mjs (the .mjs extension tells Node this is an ES module that uses import; more on that in the topic "ES Modules in Node.js"):
import { writeFile, readFile } from 'node:fs/promises';
import os from 'node:os';
await writeFile('note.txt', 'Written by Node.js'); // create a file on disk
const text = await readFile('note.txt', 'utf8'); // read it back
console.log(text);
console.log(`OS: ${os.type()}, CPU cores: ${os.availableParallelism()}`);
console.log(`Home folder: ${os.homedir()}`);Written by Node.js
OS: Linux, CPU cores: 4
Home folder: /home/ayselAfter running it, a note.txt file appears next to the script. Built-in modules are imported with the node: prefix, which makes it obvious that they are part of Node rather than npm packages. The node:fs/promises module works with files, and node:os provides system information.
Node.js and the browser: same language, different environment
The language is the same, the ECMAScript standard: variables, functions, classes, promises and async/await all work identically. What differs is the environment, the set of global objects and APIs the runtime provides:
| Browser | Node.js | |
|---|---|---|
| Global object | window (and globalThis) | globalThis (legacy name: global) |
| Page and DOM | document, querySelector, click events | none |
| Dialogs | alert, prompt, confirm | none |
| File system | none (only files the user picks) | node:fs |
| Process information | none | process: version, environment variables, arguments |
| Binary data | ArrayBuffer, Blob | the same + Buffer |
| Shared Web APIs | fetch, URL, setTimeout, console, structuredClone, AbortController | the same ones |
Let's check it in practice. The file globals.js:
console.log(typeof window); // → undefined
console.log(typeof document); // → undefined
console.log(typeof globalThis); // → object
console.log(globalThis === global); // → true
console.log(typeof process, typeof Buffer); // → object function
console.log(typeof fetch, typeof setTimeout); // → function functionglobalThis is the standard name of the global object that works everywhere: in the browser, in Node and in workers. Use it when you write code that must run in both environments. Many Web APIs (fetch, URL, TextEncoder, AbortController, crypto.randomUUID) are built into Node and behave just like in the browser, so your frontend knowledge carries over directly.
⚠️ Since a script has full access to files and the network, only run code you trust. A script from a random repository can read the keys in your home folder or delete files; there is no browser sandbox here.
Where Node.js is used
Node shines where a program spends a lot of time waiting: for a database reply, a file, the network. While one operation waits, Node serves others, so a single process can hold thousands of simultaneous connections. Here is a minimal web server (the file server.mjs):
import { createServer } from 'node:http';
const server = createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end(`Hello from Node.js ${process.version}!\n`);
});
server.listen(3000, () => {
console.log('Server is running at http://localhost:3000');
});Run node server.mjs and open http://localhost:3000 in the browser: you will see Hello from Node.js v24.21.0!. Unlike hello.js, this process does not exit: the server waits for new requests until you press Ctrl+C. We will cover the http module in depth in a separate section.
Typical use cases:
- APIs and backends: REST and GraphQL servers built with Express, Fastify or NestJS.
- Real-time: chats, notifications, collaborative editing and online games over WebSocket and Socket.IO.
- CLI utilities: npm, ESLint, Prettier, the TypeScript compiler and hundreds of other tools are written for Node.
- SSR and full-stack frameworks: Next.js, Nuxt and Astro render pages on the server with Node.
- Build tools: Vite, webpack, Rollup; every modern frontend project is built in Node.
- Desktop: Electron (which VS Code is built on) combines Node and Chromium.
Where Node is weak
JavaScript in Node runs on a single main thread. If code spends a long time computing something (encoding video, heavy math, processing large images), the thread is busy and every other request waits. For such tasks Node has worker_threads (a separate topic in this course), or the computation is moved to a service written in another language. The rule of thumb is simple: lots of I/O and networking means Node is a great fit; heavy computation in every request calls for a different approach.
Common mistakes
1. Browser objects in Node. Code that uses document or window crashes immediately:
document.querySelector('h1'); // ❌ ReferenceError: document is not definedNode has no page and no DOM. Use globalThis for the global object, and run DOM code in the browser.
2. Node modules in frontend code. The opposite case: import fs from 'node:fs' in a script attached to an HTML page will not work because the browser has no file system access. Server code and browser code live in different environments, even though they are written in the same language.
3. "Node.js is a framework" or "a language". Node is a runtime. The language is JavaScript (or TypeScript, which compiles to JavaScript). Frameworks such as Express, Fastify and NestJS are npm packages that run on top of Node.
4. Heavy computation right inside a request handler. A loop that runs for several seconds in an HTTP request handler "freezes" the server for every user. Such tasks are moved to worker threads, background job queues or separate services.
Summary
- Node.js is a JavaScript runtime outside the browser, not a language and not a framework.
- The core of Node: the V8 engine executes JavaScript, libuv handles the event loop and asynchronous I/O, and C++ bindings connect them to the standard library.
- You run a script with
node file.js; component versions are inprocess.versionandprocess.versions. - Node has no
window,documentoralert, but it hasglobalThis,process,Bufferand access to files, the network and the OS. - Many Web APIs (
fetch,URL,setTimeout,AbortController) work the same way as in the browser. - Node is strong at I/O-heavy tasks (APIs, real-time, CLI, SSR, builds) and weak at heavy computation on the main thread.
Practice
- Create
about.jsthat prints the Node version, the V8 and libuv versions, the platform and the CPU architecture as neat lines likeV8: 13.6.233.17-node.53. - Write
env-check.jsthat detects the runtime: iftypeof window !== 'undefined', it prints "Browser"; iftypeof process !== 'undefined', it prints "Node.js". Run it in Node and paste the same code into the browser's DevTools console. - Write
system-info.mjsthat builds an object with the platform, the number of CPU cores, free memory in gigabytes and the home folder, and saves it tosystem-info.json. - A small task: extend
server.mjsso that the server replies with a greeting at/and with JSON containing the Node version, the platform and the process uptime (process.uptime()) at/info.
💡 For exercise 3 you will need
os.freemem()(bytes),JSON.stringify(data, null, 2)andwriteFilefromnode:fs/promises. In exercise 4 the request path is inreq.url, and for JSON send theContent-Type: application/jsonheader.