📦 Core Modules and NPM Ecosystem
Node.js ships with a batteries-included standard library and sits on top of npm — the largest software registry on Earth. In this lesson you'll learn how modules work (both CommonJS and modern ES Modules), tour the core modules you'll use every day, and get comfortable with package.json, versioning, and installing dependencies.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Explain the three kinds of Node.js modules: core, local, and third-party
- Compare CommonJS (
require) with ES Modules (import) and choose between them - Use essential core modules —
fs,path,os,events, andutil - Read and write a package.json and understand semantic versioning
- Install, audit, and manage dependencies with npm (and know its alternatives)
Estimated Time: 35–45 minutes • Difficulty: Beginner–Intermediate
Hands-on: Build, test, and version your own small utility module.
In This Lesson
The Module System
A module is a self-contained file of related code with its own private scope. Modules let you split a large program into small, testable pieces and reuse them across projects. Node.js recognizes three sources of modules:
- Core modules — built into Node.js (e.g.
fs,http,path). No installation needed. - Local modules — files you write and import by path (e.g.
./math.js). - Third-party modules — packages you install from npm (e.g.
express).
🧱 The LEGO analogy: Core modules are the standard bricks in the box. Local modules are the custom pieces you build for this specific model. Third-party modules are the specialty kits you buy to add wings, wheels, or lights without designing them yourself.
CommonJS vs ES Modules
Node.js supports two module formats. Knowing both matters because you'll meet both in real codebases.
CommonJS (the original)
CommonJS uses require() to import and module.exports to expose. Modules load synchronously and are cached after the first load.
// math.js — a CommonJS module
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
module.exports = { add, subtract };
// app.js — importing it
const math = require('./math');
console.log(math.add(5, 3)); // 8
console.log(math.subtract(10, 4)); // 6
ES Modules (the modern standard)
ES Modules (ESM) use import/export — the same syntax you use in the browser. Enable ESM by setting "type": "module" in package.json, or by using the .mjs file extension.
// math.js — an ES module
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
// app.js — importing it
import { add, subtract } from './math.js';
console.log(add(5, 3)); // 8
console.log(subtract(10, 4)); // 6
✅ Which should you use?
For new projects, prefer ES Modules — they're the JavaScript standard, work in the browser and Node.js alike, and support features like top-level await. Reach for CommonJS mainly when maintaining older code or a dependency that hasn't migrated.
⚠️ Don't mix them carelessly
In ESM you must include the file extension ('./math.js', not './math'), and require, __dirname, and __filename aren't defined. Use import.meta.url plus node:url/node:path when you need the current file's path in ESM.
Essential Core Modules
These come with Node.js — no install required. Import them with the node: prefix to make their origin unmistakable.
fs — File System
Reads and writes files. Prefer the promise-based API (fs/promises) with async/await over old callback style.
import { readFile, writeFile } from 'node:fs/promises';
async function run() {
try {
const data = await readFile('example.txt', 'utf8');
console.log('Content:', data);
await writeFile('copy.txt', data);
} catch (err) {
console.error('File error:', err.message);
}
}
run();
path — Cross-Platform File Paths
Builds and parses file paths correctly on Windows, macOS, and Linux — never hand-concatenate paths with slashes.
import path from 'node:path';
const full = path.join('public', 'images', 'logo.png');
console.log(full); // public/images/logo.png (or \ on Windows)
console.log(path.extname('report.pdf')); // .pdf
console.log(path.basename('/home/user/report.txt')); // report.txt
os — Operating System Info
import os from 'node:os';
console.log(`Platform: ${os.platform()}, Arch: ${os.arch()}`);
console.log(`Free memory: ${(os.freemem() / 1024 ** 3).toFixed(2)} GB`);
console.log(`CPU cores: ${os.cpus().length}`);
events — The EventEmitter
The EventEmitter class underpins Node.js's event-driven design. You emit named events and register listeners for them.
import { EventEmitter } from 'node:events';
const bus = new EventEmitter();
bus.on('order', (item) => {
console.log(`New order: ${item}`);
});
bus.emit('order', 'espresso'); // New order: espresso
util — Handy Utilities
The util module offers helpers like promisify (to modernize callback APIs) and inspect (for readable object logging).
import util from 'node:util';
const obj = { user: { name: 'Ada', roles: ['admin'] } };
console.log(util.inspect(obj, { depth: null, colors: true }));
📖 Quick reference
fs — files • path — file paths • os — system info • events — pub/sub • util — helpers • http — servers & clients • crypto — hashing/encryption • stream — chunked data.
npm: The Package Manager
npm (Node Package Manager) is the world's largest software registry — well over two million packages — and it comes bundled with Node.js. Instead of reinventing common solutions, you install trusted, tested packages.
npmjs.com] A --> C[Command-line tool
npm install / run] A --> D[package.json
project manifest] D --> D1[dependencies] D --> D2[scripts] D --> D3[metadata]
package.json — the project manifest
This file records your project's metadata, scripts, and dependencies. Create it with npm init -y.
{
"name": "my-awesome-project",
"version": "1.0.0",
"description": "A demo project",
"type": "module",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "node --watch index.js",
"test": "node --test"
},
"dependencies": {
"express": "^4.19.2"
},
"devDependencies": {
"eslint": "^9.0.0"
}
}
Installing packages
# Add a runtime dependency
npm install express
# Add a development-only dependency
npm install --save-dev eslint
# Install a specific version
npm install lodash@4.17.21
# Install a global command-line tool
npm install -g http-server
# Reproducible, CI-friendly install from the lockfile
npm ci
📖 Two files, two jobs
package.json lists dependencies with version ranges — like a recipe's ingredient list. package-lock.json pins the exact versions of every package (and its sub-packages) so every install is reproducible — like the precise shopping list of brands and quantities.
Semantic Versioning
npm packages use SemVer: MAJOR.MINOR.PATCH. Each number signals the kind of change:
| Part | Bumped when… | Example |
|---|---|---|
| MAJOR | Breaking, incompatible API changes | 4.x → 5.0.0 |
| MINOR | New features, backwards-compatible | 4.1 → 4.2.0 |
| PATCH | Backwards-compatible bug fixes | 4.2.0 → 4.2.1 |
The prefix in front of a version in package.json controls how far npm may upgrade:
^4.17.3— allow any4.x.x(minor + patch updates). The common default.~4.17.3— allow any4.17.x(patch updates only).4.17.3— exactly this version, no automatic updates.*— any version. Avoid this in production.
💡 Why the caret matters
^ is a promise from the package author: "minor and patch releases won't break you." That promise only holds when authors follow SemVer honestly — which is why the lockfile exists as your safety net.
Best Practices & Alternatives
Security & maintenance
- Run
npm auditregularly andnpm audit fixto patch known vulnerabilities. - Commit
package-lock.jsonso teammates and CI install identical trees. - Be wary of packages with huge or unfamiliar dependency chains — each is code you're trusting.
Use npm scripts as your task runner
"scripts": {
"start": "node server.js",
"dev": "node --watch server.js",
"test": "node --test",
"lint": "eslint .",
"format": "prettier --write ."
}
Run any of these with npm run <name> (or just npm start / npm test for those two).
Alternative package managers
| Tool | Notable strength |
|---|---|
| npm | Bundled with Node.js; the default everyone has |
| Yarn | Fast, deterministic; popular for monorepos (Yarn Workspaces) |
| pnpm | Saves disk space via a shared store; strict about phantom dependencies |
⚠️ Pick one lockfile per project
Don't commit both a package-lock.json and a yarn.lock. Mixing managers leads to inconsistent installs. Choose one tool per repository and stick with it.
Hands-on Exercise
🏋️ Build a String-Utils Module
Objective: Create, export, and test your own local module.
Instructions:
- Make a folder:
mkdir string-utils && cd string-utils. - Run
npm init -y, then add"type": "module"to the generatedpackage.json. - Create
index.jsexporting three functions:reverse,capitalize(title-case each word), andtruncate(str, n)(cut tonchars with an ellipsis). - Create
test.jsthat imports and exercises each function, using Node's built-inassert. - Add a
"test": "node test.js"script and run it withnpm test.
💡 Hint
Title-casing a sentence: str.split(' ').map(w => w[0].toUpperCase() + w.slice(1)).join(' '). For truncation, return the string unchanged when it's already short enough.
✅ Example solution
// index.js
export function reverse(str) {
return [...str].reverse().join('');
}
export function capitalize(str) {
return str
.split(' ')
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
.join(' ');
}
export function truncate(str, n) {
return str.length <= n ? str : str.slice(0, n) + '…';
}
// test.js
import assert from 'node:assert';
import { reverse, capitalize, truncate } from './index.js';
assert.equal(reverse('hello'), 'olleh');
assert.equal(capitalize('hello world'), 'Hello World');
assert.equal(truncate('abcdef', 3), 'abc…');
assert.equal(truncate('hi', 5), 'hi');
console.log('All tests passed ✅');
🎯 Quick Quiz
Question 1: Which syntax belongs to ES Modules (not CommonJS)?
Question 2: In the version range ^4.17.3, which update would npm not take automatically?
Question 3: What is the main job of package-lock.json?
Summary & Quiz
🎉 Key Takeaways
- Node.js modules come in three flavors: core, local, and third-party.
- CommonJS (
require) is the legacy format; ES Modules (import) are the modern standard — prefer them for new code. - Core modules like
fs,path,os,events, andutilcover most everyday needs with zero install. - package.json is your project manifest; package-lock.json guarantees reproducible installs.
- SemVer (
MAJOR.MINOR.PATCH) and range prefixes (^,~) control how dependencies update.
📚 Further Reading
- Node.js API Documentation
- npm Documentation
- Semantic Versioning Specification
- Awesome Node.js (curated packages)
🚀 What's Next?
You've seen that Node.js APIs are heavily asynchronous. Next we'll go deep on the event loop — how Node.js schedules all that async work, the phases it cycles through, and how to write async code with callbacks, Promises, and async/await without blocking.
🎉 Great progress!
You can now assemble apps from modules and packages. Time to understand what runs them.