Skip to content
Educora
Advanced22 min23 / 27

Node.js basics

Run JavaScript outside the browser: the Node.js runtime, CommonJS and ES modules, npm and `package.json`, and working with files through `fs` and `path`.

Check yourself
In this lesson you will learn
  • Explain what Node.js is and how it differs from JavaScript in the browser
  • Create a project with npm, install packages and run scripts from package.json
  • Use ES modules and the built-in fs/promises, path and process to work with files and command-line arguments

Until 2009, JavaScript lived only inside browsers. That year Ryan Dahl took Chrome's V8 engine and introduced Node.js — a runtime that runs JavaScript on servers and ordinary computers. Today Node.js powers backends (APIs), command-line tools and even desktop apps: Vite, ESLint and Prettier run on Node.js, and the VS Code editor is built on Electron, which is based on Node.js.

What Node.js is

Node.js = the V8 engine + libuv (the event loop and asynchronous I/O) + built-in modules (fs, path, http, crypto and more). Same language, different environment: there is no document or window, but there are files, the network and processes. Your JavaScript still runs on one thread with the familiar event loop, while the system handles heavy I/O in the background.

BrowserNode.js
DOMyes (document)no
Filesonly when the user picks onefull access (fs)
Global objectwindow / globalThisglobal / globalThis
ModulesES modulesES modules and CommonJS
Where the code runson the user's deviceon a server or your own computer
  1. 1
    Install

    Download the LTS (long-term support) version from nodejs.org and install it.

  2. 2
    Check

    In a terminal type node -v and npm -v — you should see the versions.

  3. 3
    Create a file

    Write console.log('Hello from Node!'); in a file called hello.js.

  4. 4
    Run it

    node hello.js. Plain node without a file opens an interactive console (REPL); type .exit to leave.

Modules: ES and CommonJS

Node.js has two module systems. CommonJS (require and module.exports) is the historical one and is everywhere in older projects. ES modules (import/export) are the standard, the same as in the browser. Choose ES modules for new projects: add "type": "module" to package.json, or give files the .mjs extension. Import built-in modules with the node: prefix: import fs from 'node:fs'.

JavaScript
// math.js
export const PI = 3.14159;
export function circleArea(r) {
  return PI * r * r;
}

// app.js
import { circleArea } from './math.js';
import os from 'node:os';

console.log(circleArea(2).toFixed(2));
console.log(`Platform: ${os.platform()}`);
Expected output
12.57
Platform: win32
Run with node app.js; you will see win32 on Windows, darwin on macOS and linux on Linux.
CommonJSES modules
Exportmodule.exports = { circleArea }export function circleArea() {}
Importconst { circleArea } = require('./math')import { circleArea } from './math.js'
The file's folder__dirnameimport.meta.dirname
Top-level awaitnoyes

npm and package.json

npm (Node Package Manager) comes with Node.js and gives access to a registry with millions of packages. npm init -y creates package.json, npm install dayjs adds a dependency, and npm install -D vitest adds a development-only dependency. The packages' code goes into the node_modules folder, while package-lock.json records the exact versions. npm run dev runs a command from the scripts section, and npx runs a package's command without installing it globally.

JSON
{
  "name": "notes-cli",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "start": "node notes.js",
    "test": "vitest"
  },
  "dependencies": {
    "dayjs": "^1.11.10"
  },
  "devDependencies": {
    "vitest": "^2.1.0"
  }
}
Versions use major.minor.patch: ^1.11.10 allows any newer version below 2.0.0, ~1.11.10 only 1.11.x patches, and 1.11.10 only that exact version.

Files, paths and the process

node:fs/promises provides asynchronous file functions: readFile, writeFile, mkdir, readdir. node:path builds paths correctly both on Windows (\) and on Linux and macOS (/). process.argv holds the command-line arguments and process.env the environment variables. The small program below stores notes in a JSON file.

JavaScript
// notes.js
import { readFile, writeFile, mkdir } from 'node:fs/promises';
import path from 'node:path';

const dir = path.join(import.meta.dirname, 'data');
const file = path.join(dir, 'notes.json');
await mkdir(dir, { recursive: true });

let notes = [];
try {
  notes = JSON.parse(await readFile(file, 'utf8'));
} catch (error) {
  if (error.code !== 'ENOENT') throw error;
}

const text = process.argv.slice(2).join(' ') || 'Empty note';
notes.push({ id: notes.length + 1, text });
await writeFile(file, JSON.stringify(notes, null, 2));

console.log(`Saved note #${notes.length}: ${text}`);
console.log('File:', path.basename(file), '| notes:', notes.length);
Expected output
Saved note #1: Buy bread
File: notes.json | notes: 1
Saved note #2: Call Leyla
File: notes.json | notes: 2
First node notes.js Buy bread was run, then node notes.js Call Leyla. ENOENT means “no such file” — on the first run that is normal.

You don't even need a library to build a web server in Node.js — the built-in node:http module is enough. Real projects usually use frameworks such as Express or Fastify, but they are all built on this foundation.

JavaScript
// server.js
import http from 'node:http';

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ message: 'Hello from Node', path: req.url }));
});

server.listen(3000, () => console.log('Server on http://localhost:3000'));
Expected output
Server on http://localhost:3000
Open http://localhost:3000/api/hello in a browser — the answer is {"message":"Hello from Node","path":"/api/hello"}. Press Ctrl+C in the terminal to stop the server.
Exercise

Here process.argv is imitated by the argv variable. Write parseArgs(args): --key value pairs become { key: 'value' }, and a flag with no value after it (--verbose) becomes true.

Exercise · JavaScript
const argv = ['node', 'app.js', '--city', 'Baku', '--days', '3', '--verbose'];

function parseArgs(args) {
  const options = {};
  // walk through args; --key value → options[key] = value, lone --flag → true
  return options;
}

console.log(parseArgs(argv.slice(2)));
console.log(parseArgs(['--help']));
▸ Expected output
{ city: 'Baku', days: '3', verbose: true }
{ help: true }
Exercise

Write satisfiesCaret(version, range): for a range such as ^1.4.0, the version must have the same major number and must not be lower than the version in the range (ignore the special rules for 0.x).

Exercise · JavaScript
function satisfiesCaret(version, range) {
  // compare major, then minor, then patch
}

for (const version of ['1.5.2', '1.4.0', '2.0.0', '1.3.9']) {
  console.log(version, satisfiesCaret(version, '^1.4.0'));
}
▸ Expected output
1.5.2 true
1.4.0 true
2.0.0 false
1.3.9 false

Key points

  • Node.js = V8 + an event loop (libuv) + built-in modules: JavaScript without the DOM but with files, the network and processes.
  • Use ES modules in new projects ("type": "module", the node: prefix); require remains in older code.
  • package.json describes the project: scripts, dependencies, devDependencies; package-lock.json pins the versions, and node_modules is never committed.
  • Semver is major.minor.patch: ^ allows minor and patch updates, ~ only patches.
  • fs/promises, path and process handle files, portable paths, arguments and environment variables.

Check yourself

10 questions. Every correct answer earns XP.

1 / 10
What is not available in Node.js by default?