TypeScript with Node.js

Introduction

Node.js runs JavaScript, not TypeScript directly. For servers, CLIs, and scripts, you compile or transpile TypeScript to JavaScript, then run with node. This chapter covers a typical Node + TypeScript setup: tsconfig, @types/node, production builds with tsc, and developer workflows with tsx and file watching.

Prerequisites

Project Skeleton

bash
mkdir ts-node-api
cd ts-node-api
npm init -y
npm install --save-dev typescript @types/node tsx
npx tsc --init

tsconfig.json for modern Node ESM:

package.json:

json
{
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node --enable-source-maps dist/index.js",
    "dev": "tsx watch src/index.ts",
    "typecheck": "tsc --noEmit"
  }
}

Code explanation:

  • "type": "module" enables native ESM in Node
  • build emits dist/; start runs compiled output
  • dev uses tsx for fast reload during development

Hello Server in TypeScript

src/index.ts:

typescript
import http from "node:http";
 
const port = Number(process.env.PORT) || 3000;
 
const server = http.createServer((_req, res) => {
  res.writeHead(200, { "Content-Type": "application/json" });
  res.end(JSON.stringify({ ok: true, service: "ts-node-api" }));
});
 
server.listen(port, () => {
  console.log(`Listening on http://localhost:${port}`);
});

Run development:

bash
npm run dev

Production path:

bash
npm run build
npm start

tsc in CI

json
{
  "scripts": {
    "typecheck": "tsc --noEmit"
  }
}

Run npm run typecheck in CI even when you use tsx locally—catches type errors before deploy.

nodemon Alternative

bash
npm install --save-dev nodemon
json
{
  "scripts": {
    "dev": "nodemon --watch src --ext ts --exec \"tsx src/index.ts\""
  }
}

Safe JSON Parsing

Types do not validate runtime data—narrow unknown after JSON.parse.

ESM and __dirname

typescript
import { fileURLToPath } from "node:url";
import path from "node:path";
 
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

Use when resolving paths relative to the current module in ESM.

Common Pitfalls

IssueFix
require is not definedUse import with "type": "module"
Cannot find node:fsInstall @types/node; use NodeNext resolution
Import path errorsUse .js extension in imports for NodeNext emit
Types missing for packageInstall @types/pkg or add local .d.ts

FAQ

tsc vs tsx in production?

Run compiled dist/ with node. Use tsx for dev scripts.

Do Node APIs need a bundler?

Usually no for servers—tsc output is enough.

Vitest with TypeScript?

Install vitest and share tsconfig paths—see the JavaScript testing chapter.

Bun or Deno?

Same TypeScript ideas; runtime and config differ from this Node-focused lesson.