The config is the same as for ES modules. module: "nodenext" reads "type" from package.json and checks each file the way Node.js will load it. The files below are one project, checked with TypeScript 5.9 when this site is built.
Install
TypeScript is a development dependency: it checks your code and never runs in production. @types/node gives the checker the types of Node's built-in modules (node:fs, node:http, process and the rest). Pick the @types/node major version that matches the Node.js you run, for example @types/node@24 for Node.js 24.
npm install --save-dev typescript @types/node jsconfig.json
Identical to the ES modules setup: nodenext takes the module format from package.json, so the same config checks either kind of project, and a project that mixes .cjs and .mjs files.
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"module": "nodenext",
"moduleResolution": "nodenext",
"target": "es2024",
"types": ["node"],
"skipLibCheck": true
},
"include": ["src/**/*.js"]
} npm scripts
"type": "commonjs" is also what Node.js assumes when there is no "type"; writing it makes the choice explicit, as Node.js's documentation recommends. lint checks once; lint:watch checks again on every save.
{
"type": "commonjs",
"scripts": {
"lint": "tsc -p jsconfig.json",
"lint:watch": "tsc -p jsconfig.json --watch --preserveWatchOutput"
}
} What the checker reads from require and module.exports
In a CommonJS .js file:
const { formatPrice } = require("./format")is an import; a name the module does not export is an error, with a suggestion (TS2724), as below- relative
requirecalls may leave out.js, because Node.js tries the extension for CommonJS module.exports = { … }andexports.name = …are the module's exports, typed from the JSDoc on what you assign- since TypeScript 5.8,
require()of an ES module is allowed undernodenext, as in Node.js from 20.19 and 22.12; Node.js still refuses an ES module that uses top-levelawait
const { createServer } = require("node:http");
const { formatPrise } = require("./format");error TS2724: '"./format"' has no exported member named 'formatPrise'. Did you mean 'formatPrice'?
const port = Number(process.env.PORT ?? 3000);
createServer((req, res) => {
res.end(`Today's special: ${formatPrise(1999)}`);
}).listen(port);jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"module": "nodenext",
"moduleResolution": "nodenext",
"target": "es2024",
"types": [
"node"
],
"skipLibCheck": true
},
"include": [
"src/**/*.js"
]
} The example project
src/format.js exports a typed helper with module.exports; src/main.js requires it and Node's http module and starts a server.
const { createServer } = require("node:http");
const { formatPrice } = require("./format");
const port = Number(process.env.PORT ?? 3000);
createServer((req, res) => {
res.end(`Today's special: ${formatPrice(1999)}`);
}).listen(port);jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"module": "nodenext",
"moduleResolution": "nodenext",
"target": "es2024",
"types": [
"node"
],
"skipLibCheck": true
},
"include": [
"src/**/*.js"
]
} /**
* @param {number} cents
* @returns {string}
*/
function formatPrice(cents) {
return `$${(cents / 100).toFixed(2)}`;
}
module.exports = { formatPrice };jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"module": "nodenext",
"moduleResolution": "nodenext",
"target": "es2024",
"types": [
"node"
],
"skipLibCheck": true
},
"include": [
"src/**/*.js"
]
} Under TypeScript 6.0 and 7.0
Every example on this page also runs under TypeScript 6.0.3 and 7.0.2 when this site is built. One of them reports something different from 5.9.3, the version the page describes; the ✗ on its badge links to its entry below.
A misspelled require name loses its hint
The pattern: const { formatPrise } = require("./format") for an export named formatPrice.
Why: 5.9 reports TS2724 with "Did you mean 'formatPrice'?"; TypeScript 7.0.2 reports TS2305, the same missing export without the suggestion. The port's list of changes does not mention it. Source: CHANGES.md: changes between TypeScript 6 and the native port.
A misspelled name in a destructured require src/main.js
- TypeScript 5.9.3
src/main.js(3,9): error TS2724: '"./format"' has no exported member named 'formatPrise'. Did you mean 'formatPrice'?
- TypeScript 6.0.3same as 5.9.3
The same errors as 5.9.3.
- TypeScript 7.0.2differs from 5.9.3
src/main.js(3,9): error TS2305: Module '"./format"' has no exported member 'formatPrise'.
What works in every version: Fix the name; nothing else changes.
The example The server, which loads a helper with require on this page shows it, with the same result under every version.
In your editor
VS Code reads this jsconfig.json with no further setup. To make it use the same TypeScript as npm run lint, see VS Code.
Sources
- Modules: Packages, "type" · Node.js docs
- Modules: CommonJS modules · Node.js docs
- Type Checking JavaScript Files · TypeScript handbook
- Modules: Reference · TypeScript handbook
- TypeScript 5.8 release notes · TypeScript handbook
- @types/node · npm
Last reviewed 20 October 2026.