Convenient development with Node.js 24 features

March 2, 2026 By Nicolas Even

What we want to do

Run TypeScript files directly:

node index.ts

Use ES Modules and Top Level awaits:

import { transform } from "./transform.ts";
import { readFile } from "fs/promises";

const f = await readFile("./file");
await transform(f);

Run tests:

node --test

Restart the process when the source files change:

node --watch index.ts

Read .env files without dotenv.

Read command-line arguments.

How to do it

Typescript, ES Modules and Top Level awaits

Create a package.json that looks like this:

{
"type": "module",
"scripts": {
"test": "node --test",
"start": "node index.ts"
},
"devDependencies": {
"@types/node": "^24.0.14",
"typescript": "^5.8.3"
}
}

The important line above is "type": "module". It enables ES Modules by default, without having to use the .mts extension.

Create a tsconfig.json that looks like that:

{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"rewriteRelativeImportExtensions": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}

The most important options here are:

These options not only make sure the code is compiled correctly, but also make autocompletion work as expected (for ex. auto-creating the import statements with the .ts extension, or automatically adding the type qualifier to the imports).

Limitations:

When running node index.ts, Node.js erases the type information. This means:

Tests

Just create test files:

// operations.test.ts
import test from "node:test";
import { strictEqual } from "node:assert";
import { add } from "./operations";

test("addition", () => {
strictEqual(add(1, 2), 3);
});

.env files

import { loadEnvFile } from "node:process";
loadEnvFile();

An alternative way to do that is to use parseEnv, but it doesn’t merge the environment into process.env:

import { parseEnv } from "node:util";
import { readFile } from "node:fs/promises";

const env = await parseEnv(await readFile(".env", "utf-8"));

Command-Line arguments

import { parseArgs } from "node:util";
import { argv } from "process";

const {
values: { url, timeout },
} = parseArgs({
args: argv.slice(2),
options: {
url: { type: "string" },
timeout: { type: "string", default: "600" },
},
});

The function is pretty basic (it doesn’t support required arguments, doesn’t generate a --help command), but it’s largely good enough for small scripts.

Restart your server when the source files change

No setup required 🎉, just run:

node --watch index.ts