ES modules (ESM) are now a first-class way to write Node.js code. Node supports both CommonJS and ESM, and you can opt into ESM using file extensions or package.json settings. This update gives you two clear paths: JavaScript or TypeScript.

Option A: JavaScript (ESM)

1. Create a project

mkdir node-esm
cd node-esm
npm init -y

2. Tell Node to treat .js as ESM

Add this to package.json:

{
  "type": "module"
}

Node will treat .js files as ESM in this package scope. If you prefer, you can also use .mjs for ESM files and .cjs for CommonJS. When there are no explicit markers, Node inspects the source to decide whether a file is ESM or CommonJS. Use explicit markers for clarity.

3. Write ESM code

Create server.js:

import http from "node:http";

const hostname = "127.0.0.1";
const port = 8000;

const server = http.createServer((req, res) => {
  res.writeHead(200, { "Content-Type": "text/plain" });
  res.end("Hello World\n");
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

Run it:

node server.js

4. Remember: file extensions are required

When importing local files in ESM, include the file extension:

import { handler } from "./handler.js";

Option B: TypeScript (ESM)

TypeScript supports Node-style ESM with modern module settings. The recommended setup uses nodenext (or node18 / node20) for both module and moduleResolution.

1. Create the project

mkdir node-ts-esm
cd node-ts-esm
npm init -y
npm install -D typescript

2. package.json

{
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node dist/server.js"
  }
}

3. tsconfig.json

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "outDir": "dist",
    "rootDir": "src",
    "strict": true
  }
}

4. Write TypeScript

Create src/server.ts:

import http from "node:http";

const hostname = "127.0.0.1";
const port = 8000;

const server = http.createServer((req, res) => {
  res.writeHead(200, { "Content-Type": "text/plain" });
  res.end("Hello World\n");
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

Build and run:

npm run build
npm start

5. File extensions and ESM vs CJS in TS

  • .mts and .mjs are always ESM.
  • .cts and .cjs are always CommonJS.
  • .ts and .js follow the nearest package.json type value.

This gives you the option to mix formats when needed, without changing the entire project.

Quick checklist

  • Prefer "type": "module" for ESM-first projects.
  • Use .mjs/.cjs (or .mts/.cts) when you need mixed module formats.
  • Always include file extensions in local ESM imports.
  • In TypeScript, use nodenext (or node18 / node20) for Node-style ESM.

References