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:

ComponentLanguageWhat it does
V8C++Chrome's JavaScript engine: parses code, compiles it to machine code (JIT), manages memory and garbage collection
libuvCThe event loop and asynchronous I/O: files, network, timers, a thread pool; works the same on Linux, macOS and Windows
C++ bindingsC++The "bridge" between JavaScript and system libraries: when you call fs.readFile, a binding passes the request to libuv
Standard libraryJavaScriptBuilt-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":

bash
node -p "process.versions"
text
{
  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:

js
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):

bash
node hello.js
text
Hello from Node.js!
5
Node version: v24.21.0
Platform: linux, architecture: x64

console.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"):

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()}`);
text
Written by Node.js
OS: Linux, CPU cores: 4
Home folder: /home/aysel

After 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:

BrowserNode.js
Global objectwindow (and globalThis)globalThis (legacy name: global)
Page and DOMdocument, querySelector, click eventsnone
Dialogsalert, prompt, confirmnone
File systemnone (only files the user picks)node:fs
Process informationnoneprocess: version, environment variables, arguments
Binary dataArrayBuffer, Blobthe same + Buffer
Shared Web APIsfetch, URL, setTimeout, console, structuredClone, AbortControllerthe same ones

Let's check it in practice. The file globals.js:

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 function

globalThis 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):

js
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:

js
document.querySelector('h1'); // ❌ ReferenceError: document is not defined

Node 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 in process.version and process.versions.
  • Node has no window, document or alert, but it has globalThis, process, Buffer and 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

  1. Create about.js that prints the Node version, the V8 and libuv versions, the platform and the CPU architecture as neat lines like V8: 13.6.233.17-node.53.
  2. Write env-check.js that detects the runtime: if typeof window !== 'undefined', it prints "Browser"; if typeof process !== 'undefined', it prints "Node.js". Run it in Node and paste the same code into the browser's DevTools console.
  3. Write system-info.mjs that builds an object with the platform, the number of CPU cores, free memory in gigabytes and the home folder, and saves it to system-info.json.
  4. A small task: extend server.mjs so 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) and writeFile from node:fs/promises. In exercise 4 the request path is in req.url, and for JSON send the Content-Type: application/json header.