Handbooks / Node.js / Chapter 1

Modules and npm

7 pages · ~5 min✓ Reviewed

Start here. Next up: The Node.js Event Loop.

Node.jsPage 1

CommonJS vs ES Modules

1. Loading model

CommonJSESM
Syntaxrequire() / module.exportsimport / export
Loadingsync, whole file runs at the call, cached in require.cacheparsed first, async
Positionanywhere, computed pathhoisted, literal string
  • Conditional load: import('./x.js') returns a Promise

2. Which system per file

  • .mjs always ESM, .cjs always CJS
  • .js + "type": "module" in nearest package.json = ESM
  • .js default ("commonjs") = CJS

3. Example and gotchas

js
const fs = require('fs');
module.exports = { run };

CJS

js
import fs from 'fs';
export const run = () => {};

ESM

  • Relative imports need the extension: ./util.js
  • No __dirname or require: use import.meta.url or import.meta.dirname
Node.jsPage 2

package.json Essentials

1. Identity and Entry

  • name + version required to publish; name lowercase, no spaces
  • private: true blocks accidental npm publish
  • main: the CJS entry file
  • "type": "module": .js files become ESM
  • exports: maps subpaths and import/require; unlisted deep imports blocked; beats main

2. Starter File

  • npm init -y = defaults; npm init asks questions
json
{
  "name": "app",
  "version": "1.0.0",
  "type": "module",
  "scripts": { "start": "node index.js" }
}

3. Mistakes

  • Trailing comma or comment: strict JSON breaks
  • Cannot use import statement outside a module: older Node or "type": "commonjs"
  • Node 22.7+ runs typeless ESM with a warning; still add "type": "module"
  • Hand edit without npm install: lockfile out of sync

4. Interview Point

  • main/module/exports? main CJS entry; module bundler-only hint; exports wins
  • "type": "module"? .js parsed as ESM
  • private: true? stops accidental publish of an app
Node.jsPage 3

dependencies vs devDependencies

1. Runtime vs Build

dependenciesdevDependencies
Needed forruntime in production (express, react)build, test, lint (jest, eslint, typescript, vite)
Installnpm i pkgnpm i -D pkg

2. What a Production Install Skips

  • npm install --omit=dev skips devDependencies
  • Older flag: --production; NODE_ENV=production does the same
  • Installing your library gets its dependencies only, never its devDependencies

3. Other Dependency Kinds

FieldMeaning
peerDependenciesthe host app must supply it (a React plugin needs react); auto-installed since npm 7
optionalDependenciesmay fail to install without breaking the install
  • dependencies: what you import
  • peerDependencies: what you share with the host

4. Mistakes

  • typescript in devDependencies but compiled on deploy: the production install omits it, the build fails
  • Test libraries in dependencies: bloated images and installs
Node.jsPage 4

Semver and Lockfiles

1. Semver and Ranges

  • MAJOR.MINOR.PATCH, e.g. 2.4.1
  • PATCH: bug fix
  • MINOR: backward-compatible feature
  • MAJOR: breaking change
RangeAllows
^1.2.31.x.x at or above 1.2.3 (default)
~1.2.31.2.x only
1.2.3exact
^0.2.30.2.x only: 0.x may break

2. The Lockfile

  • package-lock.json pins exact version, tarball URL, integrity hash
  • Covers the whole tree, incl. transitive deps
  • Commit it
CommandBehaviour
npm installrespects lock, may update it
npm cistrictly from lock, wipes node_modules, fails on mismatch: use in CI

3. Mistakes

  • Deleting the lockfile or committing two (npm + yarn): each machine gets other versions
  • Never hand-edit the lockfile
  • npm outdated shows what can change
  • npm update moves within ranges
Node.jsPage 5

npm Scripts and Everyday Commands

1. Scripts

  • npm run <name> runs a script; test, start, stop skip run
  • node_modules/.bin is on the PATH during a script
  • So "lint": "eslint ." needs no global install

2. Hooks and Arguments

HookRuns
pretest, postbuildautomatically before / after that script
prepareafter local npm install, before publish
  • prepare is the place to build or install git hooks
  • Everything after -- is forwarded to the script
  • && chains steps: each must succeed (tsc && node dist)
  • npx <tool> runs a binary without a permanent install
bash
npm run test -- --watch
npx eslint .

-- forwards --watch to the test script

3. Everyday Commands

CommandDoes
npm installinstall everything
npm i pkgadd a dependency
npm i -D pkgadd a devDependency
npm uninstall pkgremove it
npm lsshow the tree
npm outdatedshow newer versions
npm updateupdate within ranges
  • Global -g tools cause version drift
  • rm -rf in scripts breaks Windows teammates
  • Forgetting -- before forwarded flags
Node.jsPage 6

Cheat Sheet: Modules and npm

1. Modules

CJSESM
Syntaxrequire, module.exportsimport, export
Loadsync, dynamicstatic, async
Needsdefault for .js"type": "module" or .mjs
Relative pathno extensionextension required
  • CJS loads ESM: import(), or require() on Node 22.12+/20.19+ (no top-level await)
  • ESM loads CJS: default import

2. npm

FieldHolds
dependenciesruntime
devDependenciesbuild, test (-D)
peerDependencieshost app supplies it
  • npm i --omit=dev skips dev deps
  • ^ same major (default), ~ same minor, none = exact
  • ^0.2.3 = 0.2.x only
  • npm run <name>, test/start shortcut, pre/post hooks, args after --
  • .bin on PATH; npx runs one-off tools
Lockfile

Commit it, never hand-edit. npm ci in CI, npm install locally.

3. Fix-it list

  • Cannot use import statement: add "type": "module" or .mjs (older Node)
  • require/__dirname not defined: in ESM, use import(), import.meta
  • ERR_MODULE_NOT_FOUND: missing file extension
  • npm ci lock mismatch: npm install, commit lock
Node.jsPage 7

Check yourself

A file app.mjs runs console.log(__dirname). What happens, and what replaces it?
  • ReferenceError: ESM has no __dirname.
  • Use import.meta.dirname, or derive it from import.meta.url.
package.json says ^0.2.3. The registry has 0.2.9, 0.3.0 and 1.0.0. Which version installs?
  • 0.2.9.
  • Below 1.0.0, ^ pins the minor, because a minor bump may break.
A Dockerfile runs npm ci --omit=dev, then npm run build, which calls tsc. typescript is a devDependency. What breaks?
  • tsc was never installed, so the build fails.
  • Build before omitting dev packages, or use a multi-stage build.