Getting Started
REPL & Running Scripts
The REPL (Read-Eval-Print Loop) is an interactive shell for experimenting with Node.js code. Use node --watch for development auto-restart. ES modules require type:module in package.json or .mjs extension.
# start the REPL (interactive shell)
node
# run a script
node app.js
# run with ES modules (type: module in package.json)
node --input-type=module app.js
# execute inline code
node -e "console.log(process.version)"
# watch mode (auto-restart on change)
node --watch app.jsVersion & Runtime Flags
node --version prints the runtime version. --trace-warnings shows stack traces for runtime warnings. NODE_OPTIONS is a single env var holding CLI flags, handy inside containers or npm scripts. --inspect opens the Chrome DevTools debugger.
# check version
node --version # v20.11.0
node -p "process.versions.v8"
# enable warning stack traces
node --trace-warnings app.js
# pass NODE_OPTIONS (max ~128KB string)
NODE_OPTIONS="--max-old-space-size=4096" node app.js
# inspect memory usage at runtime
node --inspect app.jspackage.json & Project Init
npm init creates package.json. The "type" field decides the default module system: "module" enables ES module syntax in .js files, "commonjs" (default) keeps require/module.exports. "main" is the entry point when the package is required.
# scaffold a project (answer prompts)
npm init
# non-interactive defaults
npm init -y
# "type" controls module system
# "commonjs" (default) -> require/module.exports
# "module" -> import/export
{
"name": "my-app",
"version": "1.0.0",
"type": "module",
"main": "index.js",
"scripts": { "start": "node index.js" }
}CommonJS vs ES Modules
Node supports two module systems. CommonJS (require/module.exports) is synchronous and the historical default. ES Modules (import/export) are the modern standard. The file extension (.mjs/.cjs) or package.json "type" field decides which parser is used for .js files.
# CommonJS (.cjs or type:commonjs) ----
const fs = require('fs'); // import
module.exports = { greet: () => 'hi' }; // export
# ES Modules (.mjs or type:module) -------
import fs from 'fs'; // import
export function greet() { return 'hi'; } // named export
export default { greet }; // default export
# .mjs is always ESM, .cjs is always CJS
node file.mjs # forced ESM
node file.cjs # forced CJSGlobal Objects
Node provides globals like process, console, Buffer, and timers without imports. __dirname and __filename are CommonJS-only — in ES Modules use import.meta.url instead. global is the root scope object (like window in browsers).
# available everywhere, no import needed
global; // global namespace object
process; // env, argv, stdin/stdout
console; // log, error, warn, table
setTimeout; // schedule a callback
setInterval; // schedule repeating callback
Buffer; // binary data (Uint8Array)
queueMicrotask;// schedule microtask
structuredClone; // deep clone
# CommonJS-only globals (not in ESM)
__dirname; // current directory
__filename; // current file pathExecutable Scripts (Shebang)
A shebang line (#!/usr/bin/env node) lets a file run as a standalone command on Unix. After chmod +x, run ./cli.js directly. The package.json "bin" field maps a command name to a script, used by CLI tools published to npm so global install exposes the command.
#!/usr/bin/env node
// cli.js - make a file directly runnable
const name = process.argv[2] || 'world';
console.log(`Hello, ${name}!`);
# make it executable (Unix) and run directly
chmod +x cli.js
./cli.js Alice # Hello, Alice!
# or wire it up in package.json
{
"bin": { "mycli": "./cli.js" }
}Modules System
CommonJS require & exports
require() synchronously loads a CommonJS module and returns its module.exports. require resolves relative paths (./, ../), built-ins (fs), and node_modules. Always assign to module.exports (not exports) when replacing the whole export, because exports is just an alias for module.exports initially.
// math.js
function add(a, b) { return a + b; }
const PI = 3.14;
// export via module.exports
module.exports = { add, PI };
// or: exports.add = add; exports.PI = PI;
// app.js
const math = require('./math'); // .js optional
console.log(math.add(1, 2)); // 3
console.log(math.PI); // 3.14
// destructuring
const { add } = require('./math');
console.log(add(5, 7)); // 12ES Modules import/export
ES Modules use static import/export — bindings are live and hoisted. The default export is imported with any name (no braces); named exports must use braces with matching names. import * as creates a namespace object. ESM is async by design and the modern standard.
// math.mjs (or type:module)
export function add(a, b) { return a + b; }
export const PI = 3.14;
export default function square(x) { return x * x; }
// app.mjs
import square, { add, PI } from './math.mjs';
console.log(add(1, 2)); // 3
console.log(square(5)); // 25
// import all as namespace
import * as math from './math.mjs';
console.log(math.PI); // 3.14Dynamic import()
import() is a dynamic, async import usable in both CJS and ESM. It returns a Promise resolving to the module namespace, enabling lazy-loading and conditional imports — great for reducing startup time or loading optional features. Use it for code-splitting in servers too.
// dynamic import returns a Promise
async function loadPlugin(name) {
const mod = await import(`./plugins/${name}.mjs`);
return mod.default;
}
// lazy-load heavy modules on demand
if (needPdf) {
const { exportPdf } = await import('./pdf.mjs');
exportPdf(data);
}
// works in both CommonJS and ESM
import('./optional.mjs')
.then(mod => mod.run())
.catch(err => console.error('load failed', err));Built-in Modules
Node ships dozens of built-in modules — no npm install needed. Most expose callback APIs; many also offer promise variants under the 'module/promises' path (fs/promises, dns/promises, stream/promises). require('module') (no ./) resolves to built-ins first, then node_modules.
// core modules - no install, just require/import
const fs = require('fs'); // file system
const path = require('path'); // path handling
const http = require('http'); // HTTP server/client
const os = require('os'); // OS info
const crypto = require('crypto'); // hashing/encryption
const { EventEmitter } = require('events');
const { Buffer } = require('buffer');
const stream = require('stream'); // streams
const net = require('net'); // TCP
const child_process = require('child_process');
// some are promise-based (Node 10+)
const fsp = require('fs/promises'); // async/await fsModule Caching & Circular Deps
require() caches modules by resolved path, so the same module code runs once and exports are reused. Circular dependencies work but the first-loaded module sees an incomplete (partial) exports object of the second — design around them. delete require.cache to hot-reload during development.
// modules are cached after first load
// a.js
require('./b'); // b loads, sets module.exports
const b = require('./b'); // returns cached b (same object)
console.log(b === require('./b')); // true
// circular dependency: a -> b -> a
// a.js: const b = require('./b'); b.f();
// b.js: const a = require('./a'); // a is PARTIAL here!
// module.exports = { f: () => a.x };
// inspect the cache
console.log(require.cache); // map of loaded modules
delete require.cache[require.resolve('./b')]; // force reload__dirname & import.meta.url
__dirname and __filename are CommonJS-only globals giving the current folder/file path. ES Modules have no such globals — derive them from import.meta.url via fileURLToPath. import.meta.url is the file:// URL of the current module and the ESM replacement for __filename.
// CommonJS - synchronous path globals
console.log(__dirname); // /app/src
console.log(__filename); // /app/src/index.js
// ES Modules - no __dirname, use import.meta
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const dataPath = join(__dirname, 'data.json');
// import.meta also carries the module URL
console.log(import.meta.url); // file:///app/src/index.mjsnpm & pnpm
npm init & package.json
npm init scaffolds a package.json. -y skips prompts with defaults. npm set stores config in ~/.npmrc for future inits. Scoped packages (@org/name) are useful for organizations and avoiding name collisions on the registry.
# create package.json interactively
npm init
# accept all defaults
npm init -y
# pnpm equivalent
pnpm init
# set author/license globally
npm set init-author-name "Alice"
npm set init-license MIT
# scope packages with @username
npm init --scope=@myorgInstalling Dependencies
npm install (no args) installs all listed deps. With a name it adds to package.json. --save-dev puts it in devDependencies (test/build tools, not shipped). ^ allows minor+patch updates, ~ allows patch only. -g installs CLI tools globally.
# install everything in package.json
npm install # alias: npm i
pnpm install # alias: pnpm i
# add a runtime dependency -> dependencies
npm install express
pnpm add express
# add a dev-only dependency -> devDependencies
npm install --save-dev jest # npm i -D jest
pnpm add -D jest
# install a specific version
npm install [email protected]
npm install lodash@^4.17.0 # caret: 4.x
npm install lodash@~4.17.0 # tilde: 4.17.x
# global install (CLI tools)
npm install -g pm2
pnpm add -g pm2npm scripts
Scripts in package.json run via npm run <name>. start and test are special (npm start / npm test). pre/post hooks (pretest, postbuild) run automatically. Use -- to forward args to the underlying command. pnpm lets you skip the word "run" for custom scripts.