- Explain what Node.js is and how it differs from JavaScript in the browser
- Create a project with
npm, install packages and run scripts frompackage.json - Use ES modules and the built-in
fs/promises,pathandprocessto 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.
| Browser | Node.js | |
|---|---|---|
| DOM | yes (document) | no |
| Files | only when the user picks one | full access (fs) |
| Global object | window / globalThis | global / globalThis |
| Modules | ES modules | ES modules and CommonJS |
| Where the code runs | on the user's device | on a server or your own computer |
- 1Install
Download the LTS (long-term support) version from nodejs.org and install it.
- 2Check
In a terminal type
node -vandnpm -v— you should see the versions. - 3Create a file
Write
console.log('Hello from Node!');in a file calledhello.js. - 4Run it
node hello.js. Plainnodewithout a file opens an interactive console (REPL); type.exitto 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'.
// 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()}`);12.57 Platform: win32
node app.js; you will see win32 on Windows, darwin on macOS and linux on Linux.| CommonJS | ES modules | |
|---|---|---|
| Export | module.exports = { circleArea } | export function circleArea() {} |
| Import | const { circleArea } = require('./math') | import { circleArea } from './math.js' |
| The file's folder | __dirname | import.meta.dirname |
Top-level await | no | yes |
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.
{
"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"
}
}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.
// 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);Saved note #1: Buy bread File: notes.json | notes: 1 Saved note #2: Call Leyla File: notes.json | notes: 2
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.
// 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'));Server on http://localhost:3000
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.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.
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 }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).
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", thenode:prefix);requireremains in older code. package.jsondescribes the project: scripts,dependencies,devDependencies;package-lock.jsonpins the versions, andnode_modulesis never committed.- Semver is
major.minor.patch:^allows minor and patch updates,~only patches. fs/promises,pathandprocesshandle files, portable paths, arguments and environment variables.
Check yourself
10 questions. Every correct answer earns XP.