Skip to content

Node.js TypeScript Setup — Complete Guide

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Node.js TypeScript Setup. We cover key concepts, practical examples, and best practices to help you master this topic.

Setting up Node.js with TypeScript requires configuring the compiler for Node.js module systems, installing Node.js type definitions, and choosing a runtime (ts-node, tsx, or compiled output) for development and production.

What You'll Learn

  • Installing @types/node
  • ts-node vs tsx for development
  • ESM vs CJS configuration
  • Production build setup
  • Debugging Node.js TypeScript

Why It Matters

Node.js without TypeScript is error-prone — fs.readFileSync returns Buffer | string, callback parameters are untyped, and configuration files lack autocompletion. TypeScript brings the same safety to the backend as it does to the frontend.

Real-World Use

Every DodaTech backend service — from the Durga Antivirus Pro API server to the Doda Browser sync service — is written in TypeScript and runs on Node.js. The standard setup uses tsx for development and compiled JS for production.

Learning Path

flowchart LR
  A[React Advanced] --> B[Node Setup]
  B --> C[Express APIs]
  B --> D[You Are Here]
  C --> E[Next.js]
  E --> F[NestJS]

Installing Dependencies

mkdir my-api && cd my-api
npm init -y
npm install --save-dev typescript @types/node tsx

@types/node provides type definitions for all Node.js built-in modules (fs, path, http, etc.).

tsconfig for Node.js

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "sourceMap": true,
    "declaration": true
  },
  "include": ["src"]
}

Development with tsx

tsx (TypeScript Execute) runs TypeScript directly without compilation:

npm install --save-dev tsx
// package.json
{
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js"
  }
}
// src/index.ts
import { readFile, writeFile } from 'fs/promises';
import { join } from 'path';

async function readConfig(): Promise<Record<string, unknown>> {
  const configPath = join(process.cwd(), 'config.json');
  const content = await readFile(configPath, 'utf-8');
  return JSON.parse(content);
}

async function main() {
  const config = await readConfig();
  console.log('Server starting with config:', config);
}

main().catch(console.error);

ESM vs CJS

// package.json
{
  "type": "module"
}
// Must use import/export syntax
import { readFile } from 'fs/promises';
// Import with .js extension (even for .ts files with Node16 resolution)

CJS (Legacy or specific requirements)

// package.json
{
  "type": "commonjs"
}
// import/export compiles to require/module.exports
import { readFile } from 'fs/promises';

Production Build

npm run build
# Outputs to dist/:
# - Compiled JS files
# - Source maps
# - Declaration files (.d.ts)
{
  "scripts": {
    "build": "tsc",
    "start": "node --enable-source-maps dist/index.js",
    "dev": "tsx watch src/index.ts"
  }
}

Debugging

VS Code Launch Configuration

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Debug API",
      "runtimeExecutable": "npx",
      "runtimeArgs": ["tsx"],
      "args": ["src/index.ts"],
      "console": "integratedTerminal",
      "sourceMaps": true
    }
  ]
}

Environment Variables

// src/config.ts
import 'dotenv/config';

interface AppConfig {
  port: number;
  databaseUrl: string;
  apiKey: string;
  nodeEnv: 'development' | 'production' | 'test';
}

function getConfig(): AppConfig {
  return {
    port: parseInt(process.env.PORT || '3000', 10),
    databaseUrl: process.env.DATABASE_URL || 'postgres://localhost:5432/app',
    apiKey: process.env.API_KEY || '',
    nodeEnv: (process.env.NODE_ENV as AppConfig['nodeEnv']) || 'development',
  };
}

export const config = getConfig();

Common Mistakes

1. Forgetting @types/node

Without it, fs, path, http, and other Node.js modules are typed as any.

2. Wrong Module Resolution for Node.js

Use Node16 or NodeNext for modern Node.js projects. bundler resolution may not work correctly with Node.js module system.

3. Using ts-node Instead of tsx

ts-node is slower and has compatibility issues. tsx is faster and supports ESM natively.

4. Not Adding type: "module" for ESM

Without it, Node.js treats .js files as CJS even if TypeScript outputs ESM syntax.

5. Hardcoding Configuration

Always use environment variables or config files managed by the deployment environment.

Practice Questions

  1. What does @types/node provide? Type definitions for Node.js built-in modules (fs, path, http, process, etc.).

  2. What is the difference between tsx and ts-node? tsx is faster, uses esbuild, supports ESM natively. ts-node is slower and has compatibility issues.

  3. Why use Node16 module resolution? It matches Node.js's native module resolution, supporting both ESM and CJS with correct extensions.

  4. What does tsx watch do? Runs TypeScript files and automatically restarts when files change.

Challenge: Set up a Node.js TypeScript project with ESM, dotenv, and a production build pipeline. Create a simple HTTP server that reads environment variables and returns them as JSON.

FAQ

Can I run TypeScript directly in production?

You can use tsx in production, but compiled JavaScript is preferred for better performance and smaller Docker images.

What is the difference between `module: "Node16"` and `module: "ESNext"`?

Node16 respects Node.js's ESM/CJS detection. ESNext always outputs ES modules regardless of context.

Do I need Babel with TypeScript on Node.js?

No. TypeScript compiler (tsc) handles both type checking and Transpilation for Node.js.

How do I handle environment variables with TypeScript?

Use dotenv for .env files and type the config object as shown above.

What is `--enable-source-maps` in Node.js?

It enables Node.js to read source maps and show TypeScript line numbers in stack traces.

Mini Project: TypeScript Node.js Server

// src/server.ts
import { createServer, IncomingMessage, ServerResponse } from 'http';
import { readFile } from 'fs/promises';
import { join, extname } from 'path';

const PORT = parseInt(process.env.PORT || '3000', 10);
const MIME_TYPES: Record<string, string> = {
  '.html': 'text/html',
  '.js': 'text/javascript',
  '.css': 'text/css',
  '.json': 'application/json',
};

async function handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
  try {
    const url = req.url === '/' ? '/index.html' : req.url!;
    const filePath = join(process.cwd(), 'public', url);
    const ext = extname(filePath);
    const content = await readFile(filePath);
    res.writeHead(200, { 'Content-Type': MIME_TYPES[ext] || 'text/plain' });
    res.end(content);
  } catch {
    res.writeHead(404, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ error: 'Not found' }));
  }
}

const server = createServer(handleRequest);
server.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`));

What's Next

Now build typed Express APIs:

Lesson Description
{{< ref "/programming-languages/typescript/42-react-advanced" >}} Review React advanced
{{< ref "/programming-languages/typescript/44-express-apis" >}} Express with typed middleware and requests
{{< ref "/programming-languages/typescript/45-nextjs" >}} Next.js App Router with TypeScript

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro