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
.mtsand.mjsare always ESM..ctsand.cjsare always CommonJS..tsand.jsfollow the nearestpackage.jsontypevalue.
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(ornode18/node20) for Node-style ESM.