Modules and Imports
Module system idioms for modern Node.js - ESM first, with CJS interop when you need it. Results appear in the same fence: same-line // comments when short, multiline // blocks below the sample when not.
Search across all documentation pages
Module system idioms for modern Node.js - ESM first, with CJS interop when you need it. Results appear in the same fence: same-line // comments when short, multiline // blocks below the sample when not.
Export named bindings from an ES module. Importers must use the same names or rename with as.
// math.ts
export function add(a: number, b: number) {
return a + b;
}
add(2, 3) // 5
export const PI = 3.14159; // 3.14159Default exports are common for a single primary value. Prefer named exports for better refactoring.
// greeter.ts
export default function greet(name: string) {
return `Hello, ${name}`;
}
// app.ts
import greet from "./greeter.js";
greet("Ada") // "Hello, Ada"Type-only imports erase at compile time and avoid circular runtime deps for types.
import type { User } from "./types.js";
// erased at runtime - no JS import emitted for type-onlyLoad a module lazily at runtime - useful for optional features and code splitting.
const mod = await import("./math.js");
mod.add(1, 2) // 3When an ESM file must load a CJS package that has no ESM export surface, use createRequire.
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
// const legacy = require("some-cjs-package");
typeof require // "function"In ESM there is no __dirname - derive it from import.meta.url.
import path from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
path.isAbsolute(__dirname) // true"type": "module" makes .js files ESM in that package. Use .cjs for CommonJS files beside them.
// package.json
// { "type": "module", "exports": { ".": "./dist/index.js" } }
// .js files resolve as ESM when "type": "module"The exports field routes import/require and subpaths deliberately - prefer it over leaking main only.
// package.json "exports":
// {
// ".": { "import": "./esm/index.js", "require": "./cjs/index.js" },
// "./package.json": "./package.json"
// }Prefer node: prefixes for built-ins so bare names cannot be shadowed by local packages.
import path from "node:path";
path.join("a", "b") // "a/b" (or "a\\b" on Windows)Import JSON with an import attribute when enabled; otherwise read and parse the file.
import pkg from "./package.json" with { type: "json" };
typeof pkg.version // "string"
// or: JSON.parse(await readFile(new URL("./package.json", import.meta.url), "utf8"))Re-export public API from an index module carefully - avoid circular barrels in large graphs.
// index.ts
export { add, PI } from "./math.js";
export { default as greet } from "./greeter.js";
// importers: import { add, greet } from "./index.js"Classic CommonJS export form still used by many packages and .cjs files.
// util.cjs
function clamp(n, min, max) {
return Math.min(max, Math.max(min, n));
}
module.exports = { clamp };
// require("./util.cjs").clamp(10, 0, 5) // 5Clear require.cache only in rare hot-reload tooling - never as app business logic.
// tooling only
const resolved = require.resolve("./config.cjs");
delete require.cache[resolved];
// next require("./config.cjs") reloads from diskESM allows top-level await in modules and entrypoints under Node ESM.
const n = await Promise.resolve(42);
n // 42
export const ready = Boolean(n); // trueUse node:assert/strict for simple script assertions without a test runner.
import assert from "node:assert/strict";
assert.equal(2 + 2, 4);
// throws AssertionError if unequalStack versions: Node.js 24.18.0 (LTS line 24) · TypeScript 5.6+ · npm 10+
Reviewed by Chris St. John·Last updated Jul 19, 2026