Ecosystem & Toolchain
Running TypeScript in Node.js
Section titled “Running TypeScript in Node.js”TypeScript is not natively understood by Node.js — it must be stripped or compiled first. You have three main options in the modern ecosystem:
Option 1: --experimental-strip-types (Node 20.6+ / stable Node 22)
Section titled “Option 1: --experimental-strip-types (Node 20.6+ / stable Node 22)”Node can strip TypeScript type annotations at startup without any extra package. It does not type-check — it just removes the types and runs the JavaScript:
# Node 22 — stablenode --experimental-strip-types src/app.ts
# Node 20.6+node --experimental-strip-types --input-type=module src/app.tsLimitations: no enum, no namespace, no experimentalDecorators. For those, use tsx or ts-node.
Option 2: tsx — fast TypeScript runner
Section titled “Option 2: tsx — fast TypeScript runner”tsx is a lightweight wrapper around esbuild. It handles all TypeScript features and starts in milliseconds:
# Run oncenpx tsx src/app.ts
# Watch mode — auto-restart on changesnpx tsx watch src/app.ts
# Install as a dev dependencynpm install --save-dev tsxOption 3: ts-node — the classic
Section titled “Option 3: ts-node — the classic”ts-node has been the standard for years. It is heavier than tsx but has mature configuration support:
npm install --save-dev ts-node typescript
# Run a filenpx ts-node src/app.ts
# ESM mode (tsconfig must have "module": "NodeNext")npx ts-node --esm src/app.tsLinters and Formatters
Section titled “Linters and Formatters”ESLint — catch logic errors and enforce style rules
Section titled “ESLint — catch logic errors and enforce style rules”# Install ESLint with TypeScript supportnpm install --save-dev eslint @eslint/js typescript-eslint
# Create a flat config (eslint.config.mjs)# then run:npx eslint src/
# Auto-fix fixable issuesnpx eslint --fix src/A minimal eslint.config.mjs for TypeScript:
import js from '@eslint/js';import tseslint from 'typescript-eslint';
export default tseslint.config( js.configs.recommended, ...tseslint.configs.recommended,);Prettier — opinionated code formatter
Section titled “Prettier — opinionated code formatter”npm install --save-dev prettier
# Format all filesnpx prettier --write .
# Check formatting without changing files (for CI)npx prettier --check .Prettier and ESLint focus on different things: Prettier handles formatting (spacing, quotes, semicolons), ESLint handles logic and style rules. Run both in sequence or use eslint-config-prettier to disable ESLint’s formatting rules.
Biome — one tool for both lint and format
Section titled “Biome — one tool for both lint and format”Biome replaces ESLint + Prettier with a single fast tool written in Rust:
npm install --save-dev @biomejs/biomenpx biome init
# Lint and format in one passnpx biome check --apply src/Watch Mode and nodemon
Section titled “Watch Mode and nodemon”Node built-in watch (Node 18+)
Section titled “Node built-in watch (Node 18+)”# Restart on any file change — no install requirednode --watch src/app.js
# Watch specific filesnode --watch-path=src src/app.jsnodemon — battle-tested watcher
Section titled “nodemon — battle-tested watcher”npm install --save-dev nodemon
# Basic usagenpx nodemon src/app.js
# With TypeScript via tsxnpx nodemon --exec "npx tsx" src/app.ts
# nodemon.json config{ "watch": ["src"], "ext": "ts,js,json", "exec": "npx tsx src/app.ts"}Environment Variables and .env Files
Section titled “Environment Variables and .env Files”Native .env loading (Node 20.6+)
Section titled “Native .env loading (Node 20.6+)”# Load a .env file without dotenvnode --env-file=.env src/app.js
# Load multiple env files (later files override earlier)node --env-file=.env --env-file=.env.local src/app.jsdotenv — the classic
Section titled “dotenv — the classic”npm install dotenv// src/app.js — load at the very topimport 'dotenv/config';
console.log(process.env.DATABASE_URL); // from .envBest practices for env files
Section titled “Best practices for env files”# .env — default values, committed to git (no secrets)NODE_ENV=developmentPORT=3000LOG_LEVEL=info
# .env.local — local overrides, NOT committed (in .gitignore)DATABASE_URL=postgres://localhost:5432/mydbSECRET_KEY=dev-only-secret
# .env.example — template showing required vars, committedDATABASE_URL=SECRET_KEY=Always add .env.local and .env*.local to .gitignore. Never commit real secrets.
package.json scripts — the glue
Section titled “package.json scripts — the glue”{ "scripts": { "dev": "tsx watch src/app.ts", "build": "tsc --noEmit", "start": "node dist/app.js", "test": "node --test", "lint": "eslint src/ && prettier --check .", "format": "prettier --write . && eslint --fix src/" }}