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

FeatureESM (import/export)CommonJS (require)
File extension.mjs or .js with "type":"module".cjs or .js (default)
Top-level awaitYesNo
__filename / __dirnameNot available (use import.meta)Available
Dynamic importawait import()require() (sync)
Tree-shakableYesNo
Named exportsYesVia module.exports object
Default loadingAsyncSynchronous

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"
    }
  }
}

exports takes priority over main/module. Paths not listed in exports are 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

  1. Core built-in (e.g. fs, path)
  2. Exact file path (with or without extension)
  3. index.js / index.json / index.node in directory
  4. node_modules — walks up from current directory
  5. NODE_PATH entries (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"
    }
  }
}