Node.js Cheatsheet
Modules (ESM and CommonJS)
Use this Node.js reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
ESM vs CommonJS at a Glance
| Feature | ESM (import/export) | CommonJS (require) |
|---|---|---|
| File extension | .mjs or .js with "type":"module" | .cjs or .js (default) |
Top-level await | Yes | No |
__filename / __dirname | Not available (use import.meta) | Available |
| Dynamic import | await import() | require() (sync) |
| Tree-shakable | Yes | No |
| Named exports | Yes | Via module.exports object |
| Default loading | Async | Synchronous |
ESM — Exports
// Named exports export const PI = 3.14159; export function add(a, b) { return a + b; } export class Vector { constructor(x, y) { this.x = x; this.y = y; } } // Default export (one per file) export default function greet(name) { return `Hello, ${name}`; } // Re-export from another module export { add, subtract } from "./math.js"; export * from "./utils.js"; export * as utils from "./utils.js"; // namespace re-export // Rename on export export { internalName as publicName };
ESM — Imports
// Named import import { add, PI } from "./math.js"; // Default import import greet from "./greet.js"; // Both import greet, { add } from "./module.js"; // Rename on import import { add as sum } from "./math.js"; // Namespace import (all named exports as object) import * as math from "./math.js"; math.add(1, 2); // Side-effect only import "./setup.js"; // Dynamic import (returns Promise — works in both ESM and CJS) const { add } = await import("./math.js"); const mod = await import(`./plugins/${name}.js`);
ESM — import.meta
import.meta.url // "file:///abs/path/to/file.js" import.meta.dirname // Node 21.2+ — "/abs/path/to" import.meta.filename // Node 21.2+ — "/abs/path/to/file.js" import.meta.resolve("./utils.js") // resolves specifier to URL string // Pre-21.2 __dirname / __filename shim import { fileURLToPath } from "node:url"; import { dirname } from "node:path"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename);
CommonJS — Exports
// Single value / function module.exports = function add(a, b) { return a + b; }; // Object of named values module.exports = { add, subtract, PI: 3.14 }; // Shorthand (adds to exports object — same reference as module.exports unless reassigned) exports.add = (a, b) => a + b; exports.PI = 3.14; // WARNING: never reassign exports — breaks the reference // exports = { add }; ← WRONG; use module.exports = { add } instead
CommonJS — Require
// Local file (extension optional for .js/.json/.node) const math = require("./math"); const { add, PI } = require("./math"); // Built-in module const fs = require("node:fs"); const path = require("node:path"); // npm package const express = require("express"); // JSON file (auto-parsed) const pkg = require("./package.json"); // Dynamic path const plugin = require(`./plugins/${name}`); // Inspect cache console.log(require.cache); // map of all loaded modules delete require.cache[require.resolve("./math")]; // force re-load // Check if run directly if (require.main === module) { // this file was run directly, not required }
Package.json — Module Fields
{
"name": "my-pkg",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
},
"./utils": {
"import": "./dist/utils.js",
"require": "./dist/utils.cjs"
}
}
}
exportstakes priority overmain/module. Paths not listed inexportsare not importable by consumers.
Mixing ESM and CJS
// CJS can dynamic-import ESM (async) async function loadESM() { const { default: mod } = await import("./esm-module.mjs"); return mod; } // ESM can import CJS — module.exports becomes the default export import cjsMod from "./legacy.cjs"; // Named exports ARE synthesized for most CJS modules (static analysis via // cjs-module-lexer): `exports.foo =` / `module.exports = { foo }` patterns work import { foo } from "./legacy.cjs"; // Dynamically-built exports aren't detected — fall back to the default object // CJS can require() ESM directly — stable, no flag (Node 22.12+ / 23+) const esm = require("./esm-module.mjs"); // module namespace object // esm.default is the default export; throws ERR_REQUIRE_ASYNC_MODULE // if the module graph uses top-level await // Force CJS in an ESM project // Rename file to .cjs OR put separate package.json in subfolder: // { "type": "commonjs" }
Module Resolution Order
- Core built-in (e.g.
fs,path) - Exact file path (with or without extension)
index.js/index.json/index.nodein directorynode_modules— walks up from current directoryNODE_PATHentries (legacy)
// Subpath imports (package self-referencing / internal aliasing) // package.json { "imports": { "#utils": "./src/utils.js", "#lib/*": "./src/lib/*.js" } } // Usage inside same package import { helper } from "#utils"; import { parse } from "#lib/parser";
Useful Patterns
// Lazy singleton (CJS — module is cached after first require) let _db; function getDb() { if (!_db) _db = connectDb(); return _db; } module.exports = { getDb }; // ESM top-level await for async init // db.js export const db = await connectDb(); // other importers await automatically // Conditional exports for browser vs Node { "exports": { ".": { "browser": "./dist/browser.js", "node": "./dist/node.js", "default": "./dist/node.js" } } }