Understand Node.js module selection, convert require and module.exports to import and export, replace CommonJS globals, and test interoperability before migrating a production package.
What you will build
This tutorial produces a working, verifiable result rather than a command list. Read each command before running it, keep configuration in version control without secrets, and record the versions used for repeatable deployment.
1. Prepare the project
mkdir node-esm-demo && cd node-esm-demo
npm init -y
npm pkg set type=module
mkdir src
Run preparation commands as an unprivileged application user unless a command explicitly requires sudo. A clean working tree and lockfile make rollback much easier.
2. Implement the solution
// src/math.js
export function add(a, b) { return a + b; }
// src/index.js
import { add } from './math.js';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
const filename = fileURLToPath(import.meta.url);
const directory = dirname(filename);
console.log({ result: add(2, 3), filename, directory });
Save the example in the filename indicated by its comment or surrounding instructions. Treat it as a minimal baseline: production applications should separate transport, business logic, persistence, and configuration into testable modules.
3. Verify end to end
node src/index.js
node --check src/index.js
npm pkg get type
Verification should cover both process state and a real request or data operation. A process that is merely running is not necessarily ready to serve traffic.
4. Troubleshooting and production notes
Relative ESM imports need explicit file extensions. In a type=module package, rename remaining CommonJS files to .cjs; use .mjs to force ESM in a CommonJS package. Migrate one boundary at a time and check whether dependencies expose ESM, CommonJS, or conditional exports.
Production checklist
- The supported Node.js LTS version and dependency lockfile are recorded.
- Configuration is validated at startup and secrets are stored outside source control.
- Input limits, authentication, authorization, timeouts, and error boundaries are explicit.
- Logs identify a request without exposing credentials or personal data.
- Health checks, graceful termination, resource limits, backup, and rollback have been tested.
Reference: official topic documentation. For production version selection, use a supported LTS line from the Node.js release schedule.