Phase 1 · Node.js FoundationsModule 4~42 min read

Modules & Code Organization

Split applications into focused files and create clear public APIs with both modern ES modules and CommonJS.

What you'll learn

Modules turn a growing Node.js program into explicit dependency boundaries. You will build with modern ES modules, recognize CommonJS, use node: specifiers, and keep public APIs intentionally small.

By the end of this lesson, you'll be able to:

  • Configure and use ES modules with import and export
  • Recognize CommonJS require and module.exports
  • Resolve file locations safely in an ES module
  • Design modules without hidden globals or circular dependencies

Core mental model

Node.js becomes easier when you separate the JavaScript language from the runtime and the operating-system capabilities it exposes. Use this table as a decision guide.

ConceptWhat it meansDecision rule
ES moduleThe standard static import/export formatUse it for new course projects and declare type: module in package.json
CommonJSNode's original require/module.exports formatUnderstand it for existing packages; do not mix formats accidentally
Public APIThe names a module deliberately exportsExport stable capabilities, not every internal helper
node: specifierAn explicit reference to a Node.js built-in modulePrefer node:path, node:fs, and similar forms for clarity

Professional workflow

Build and verify Node.js programs from the terminal in small, observable steps.

  1. Group behavior by responsibility rather than by arbitrary file size.
  2. Create package.json and choose one module system deliberately.
  3. Export the smallest API a consumer needs and keep helpers private.
  4. Import dependencies at the top so the module graph remains visible.
  5. Run the program from a different working directory when paths matter.
  6. Remove dependency cycles by extracting a lower-level shared boundary.

Keep the feedback loop short

Run the smallest useful command after every meaningful change. Read the complete error message before editing again, and keep inputs and outputs visible while you learn.

Guided code lab

Create a focused ES module

Only the supported calculations are exported. The formatter is an internal implementation detail.

pricing.js
function roundCurrency(value) {
  return Math.round(value * 100) / 100;
}

export function subtotal(lines) {
  return lines.reduce(
    (sum, line) => sum + line.price * line.quantity,
    0,
  );
}

export function total(lines, taxRate = 0) {
  return roundCurrency(subtotal(lines) * (1 + taxRate));
}

Consume the public API

Static imports make dependencies visible before execution. The node: prefix clearly identifies a built-in module.

app.js
import { basename } from "node:path";
import { total } from "./pricing.js";

const lines = [{ price: 12.5, quantity: 2 }];
console.log(basename(import.meta.filename));
console.log(total(lines, 0.1));

Recognize the CommonJS equivalent

CommonJS loads through require and exports through module.exports. Existing Node.js code and packages may still use it.

pricing.cjs
function subtotal(lines) {
  return lines.reduce(
    (sum, line) => sum + line.price * line.quantity,
    0,
  );
}

module.exports = { subtotal };

Production practice

One system

Choose ESM or CommonJS per package and use explicit .cjs or .mjs files only at deliberate compatibility boundaries.

Narrow exports

Treat exports as a supported contract. Private helpers can change without breaking consumers.

Acyclic graph

Dependencies should generally point toward lower-level capabilities, not circle back into application entry points.

Common failure mode

A relative file-system path is resolved from process.cwd(), not automatically from the module file. Use module-relative URLs or paths when the asset belongs to the module.

Independent workshop

Split a small order calculator into validation, pricing, formatting, and application modules using ES modules.

Your finished workshop must include:

  • A package.json declaring ES modules
  • Named exports with at least one private helper
  • No global mutable state
  • A dependency sketch with no circular imports

Definition of done

Run the happy path and at least two edge cases, keep responsibilities separated, and add a short README explaining how to run the program.

Recap & quick check

Key takeaways

  • ES modules are the standard format
  • CommonJS remains important to recognize
  • Exports define the public contract
  • node: prefixes identify built-ins
  • Module-relative and working-directory-relative paths solve different problems

Quick check

1. Which package.json field opts .js files into ES module interpretation?

2. What does node:path identify?

3. Where does CommonJS export values?

4. Why keep exports narrow?

Next: npm, Dependencies & Scripts