TypeScript with Node.js

Building type-safe server-side applications with Node.js

🚀 What is TypeScript with Node.js?

TypeScript enhances Node.js development with static typing, better tooling, and improved code quality. It catches errors during development, provides excellent autocomplete, and makes backend code more maintainable and scalable for production applications.


// Type-safe Node.js server
import express, { Request, Response } from 'express';

const app = express();

app.get('/api/hello', (req: Request, res: Response) => {
  res.json({ message: 'Hello, TypeScript!' });
});
                                    

Key Features

🔒

Type Safety

Catch errors before runtime

function getUser(id: number): User {
  return database.findUser(id);
}
🛠️

Better Tooling

IntelliSense and autocomplete

// IDE suggests properties
user.name
user.email
📦

NPM Types

Type definitions for packages

npm install --save-dev @types/node
npm install --save-dev @types/express

Modern Features

Use latest JavaScript features

const data = await fetchData();
const { name, age } = user;

🔹 Setting Up TypeScript with Node.js

Initialize a new TypeScript Node.js project:

# Create project directory
mkdir my-node-app
cd my-node-app

# Initialize npm
npm init -y

# Install TypeScript and Node types
npm install --save-dev typescript @types/node

# Create TypeScript configuration
npx tsc --init

🔹 TypeScript Configuration for Node.js

Configure tsconfig.json for Node.js:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "moduleResolution": "node"
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

🔹 Basic Node.js Server

Create a simple HTTP server with TypeScript:

🔸 src/server.ts

import http from 'http';

const PORT = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'application/json');
  res.end(JSON.stringify({ message: 'Hello from TypeScript!' }));
});

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

Build and run:

# Compile TypeScript
npx tsc

# Run compiled JavaScript
node dist/server.js

🔹 Express with TypeScript

Build a REST API with Express and TypeScript:

# Install Express and types
npm install express
npm install --save-dev @types/express

🔸 src/app.ts

import express, { Request, Response, NextFunction } from 'express';

const app = express();
const PORT = 3000;

// Middleware
app.use(express.json());

// Types
interface User {
  id: number;
  name: string;
  email: string;
}

// In-memory database
const users: User[] = [
  { id: 1, name: 'John Doe', email: '[email protected]' },
  { id: 2, name: 'Jane Smith', email: '[email protected]' },
];

// Routes
app.get('/api/users', (req: Request, res: Response) => {
  res.json(users);
});

app.get('/api/users/:id', (req: Request, res: Response) => {
  const id = parseInt(req.params.id);
  const user = users.find(u => u.id === id);
  
  if (user) {
    res.json(user);
  } else {
    res.status(404).json({ error: 'User not found' });
  }
});

app.post('/api/users', (req: Request, res: Response) => {
  const newUser: User = {
    id: users.length + 1,
    name: req.body.name,
    email: req.body.email,
  };
  
  users.push(newUser);
  res.status(201).json(newUser);
});

// Error handling
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  console.error(err.stack);
  res.status(500).json({ error: 'Something went wrong!' });
});

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

🔹 File System Operations

Work with files using TypeScript:

import fs from 'fs/promises';
import path from 'path';

// Read file
async function readFile(filename: string): Promise<string> {
  try {
    const filePath = path.join(__dirname, filename);
    const data = await fs.readFile(filePath, 'utf-8');
    return data;
  } catch (error) {
    throw new Error(`Error reading file: ${error}`);
  }
}

// Write file
async function writeFile(filename: string, content: string): Promise<void> {
  try {
    const filePath = path.join(__dirname, filename);
    await fs.writeFile(filePath, content, 'utf-8');
    console.log('File written successfully');
  } catch (error) {
    throw new Error(`Error writing file: ${error}`);
  }
}

// Usage
(async () => {
  await writeFile('data.txt', 'Hello, TypeScript!');
  const content = await readFile('data.txt');
  console.log(content);
})();

🔹 Environment Variables

Handle environment variables with type safety:

# Install dotenv
npm install dotenv
npm install --save-dev @types/dotenv

🔸 .env

PORT=3000
DATABASE_URL=mongodb://localhost:27017/mydb
API_KEY=your-secret-key

🔸 src/config.ts

import dotenv from 'dotenv';

dotenv.config();

interface Config {
  port: number;
  databaseUrl: string;
  apiKey: string;
}

function getConfig(): Config {
  const port = process.env.PORT;
  const databaseUrl = process.env.DATABASE_URL;
  const apiKey = process.env.API_KEY;

  if (!port || !databaseUrl || !apiKey) {
    throw new Error('Missing required environment variables');
  }

  return {
    port: parseInt(port, 10),
    databaseUrl,
    apiKey,
  };
}

export const config = getConfig();

🔹 Async/Await with TypeScript

Handle asynchronous operations:

// Simulated database
interface Product {
  id: number;
  name: string;
  price: number;
}

async function fetchProducts(): Promise<Product[]> {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve([
        { id: 1, name: 'Laptop', price: 999 },
        { id: 2, name: 'Mouse', price: 25 },
      ]);
    }, 1000);
  });
}

async function getProductById(id: number): Promise<Product | undefined> {
  const products = await fetchProducts();
  return products.find(p => p.id === id);
}

// Usage
(async () => {
  try {
    const product = await getProductById(1);
    if (product) {
      console.log(`Found: ${product.name} - $${product.price}`);
    } else {
      console.log('Product not found');
    }
  } catch (error) {
    console.error('Error:', error);
  }
})();

🔹 Development Tools

Install tools for better development experience:

# Install ts-node for running TypeScript directly
npm install --save-dev ts-node

# Install nodemon for auto-restart
npm install --save-dev nodemon

# Install ts-node-dev (combines both)
npm install --save-dev ts-node-dev

Update package.json :

{
  "scripts": {
    "build": "tsc",
    "start": "node dist/server.js",
    "dev": "ts-node-dev --respawn src/server.ts",
    "dev:watch": "nodemon --exec ts-node src/server.ts"
  }
}

🔹 Complete Example: REST API

Full-featured API with TypeScript:

import express, { Request, Response } from 'express';

const app = express();
app.use(express.json());

// Types
interface Task {
  id: number;
  title: string;
  completed: boolean;
  createdAt: Date;
}

// Data store
let tasks: Task[] = [];
let nextId = 1;

// GET all tasks
app.get('/api/tasks', (req: Request, res: Response) => {
  res.json(tasks);
});

// GET single task
app.get('/api/tasks/:id', (req: Request, res: Response) => {
  const task = tasks.find(t => t.id === parseInt(req.params.id));
  if (task) {
    res.json(task);
  } else {
    res.status(404).json({ error: 'Task not found' });
  }
});

// POST new task
app.post('/api/tasks', (req: Request, res: Response) => {
  const newTask: Task = {
    id: nextId++,
    title: req.body.title,
    completed: false,
    createdAt: new Date(),
  };
  tasks.push(newTask);
  res.status(201).json(newTask);
});

// PUT update task
app.put('/api/tasks/:id', (req: Request, res: Response) => {
  const id = parseInt(req.params.id);
  const taskIndex = tasks.findIndex(t => t.id === id);
  
  if (taskIndex !== -1) {
    tasks[taskIndex] = { ...tasks[taskIndex], ...req.body };
    res.json(tasks[taskIndex]);
  } else {
    res.status(404).json({ error: 'Task not found' });
  }
});

// DELETE task
app.delete('/api/tasks/:id', (req: Request, res: Response) => {
  const id = parseInt(req.params.id);
  const taskIndex = tasks.findIndex(t => t.id === id);
  
  if (taskIndex !== -1) {
    tasks.splice(taskIndex, 1);
    res.status(204).send();
  } else {
    res.status(404).json({ error: 'Task not found' });
  }
});

const PORT = 3000;
app.listen(PORT, () => {
  console.log(`Task API running on http://localhost:${PORT}`);
});

🧠 Test Your Knowledge

What package provides TypeScript type definitions for Node.js?