TypeScript and Node.js

TypeScript brings the power of static typing to Node.js development, helping you build more reliable and maintainable server-side applications. By catching errors during development rather than in production, TypeScript significantly improves the quality of your Node.js code while maintaining all the flexibility JavaScript is known for. Whether you're building APIs, microservices, or full-stack applications, combining TypeScript with Node.js creates a robust development environment.

Setting Up TypeScript with Node.js

Getting started with TypeScript in a Node.js project is straightforward. Here's how to set up a new project:

1. Initialize your project:

bash
1
          npm init -y
        

2. Install TypeScript and required dependencies:

bash
1
          npm install typescript ts-node @types/node --save-dev
        

3. Create a tsconfig.json file:

bash
1
          npx tsc --init
        

4. Configure tsconfig.json for Node.js:

json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
          {
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}
        

5. Add development scripts to package.json:

json
1
2
3
4
5
          "scripts": {
  "dev": "ts-node src/index.ts",
  "build": "tsc",
  "start": "node dist/index.js"
}
        

For existing Node.js projects, you can gradually migrate to TypeScript by:

  1. 1
    Renaming .js files to .ts one at a time
  2. 2
    Adding type annotations incrementally
  3. 3
    Using JSDoc comments for type hints during transition
  4. 4
    Configuring allowJs in tsconfig.json to mix JavaScript and TypeScript

Writing Server-Side Code with TypeScript

TypeScript enhances Node.js development by providing type safety for your server-side code. Here are key patterns and examples:

Basic Server with Express

typescript
1
2
3
4
5
6
7
8
9
10
11
12
          import express, { Application, Request, Response } from 'express';

const app: Application = express();
const PORT: number = 3000;

app.get('/', (req: Request, res: Response) => {
  res.send('Hello TypeScript with Node.js!');
});

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

Typed Database Models

typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
          interface User {
  id: string;
  name: string;
  email: string;
  createdAt: Date;
}

class UserRepository {
  private users: User[] = [];

  addUser(user: Omit<User, 'id' | 'createdAt'>): User {
    const newUser: User = {
      ...user,
      id: Math.random().toString(36).substring(2),
      createdAt: new Date()
    };
    this.users.push(newUser);
    return newUser;
  }

  getUserById(id: string): User | undefined {
    return this.users.find(user => user.id === id);
  }
}
        

Error Handling with Custom Error Types

typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
          class AppError extends Error {
  constructor(
    public message: string,
    public statusCode: number = 500,
    public details?: any
  ) {
    super(message);
    Object.setPrototypeOf(this, new.target.prototype);
  }
}

// Usage
app.get('/user/:id', (req: Request, res: Response) => {
  const user = userRepository.getUserById(req.params.id);
  if (!user) {
    throw new AppError('User not found', 404);
  }
  res.json(user);
});

// Error handling middleware
app.use((err: Error, req: Request, res: Response) => {
  if (err instanceof AppError) {
    return res.status(err.statusCode).json({
      error: err.message,
      details: err.details
    });
  }
  res.status(500).json({ error: 'Internal Server Error' });
});
        

TypeScript for API Development

TypeScript shines when building APIs, providing type safety for both request/response payloads and database operations.

REST API with Typed Routes

typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
          import { Router } from 'express';

interface Product {
  id: string;
  name: string;
  price: number;
  inStock: boolean;
}

const products: Product[] = [
  { id: '1', name: 'Laptop', price: 999, inStock: true },
  { id: '2', name: 'Mouse', price: 25, inStock: false }
];

const router = Router();

// GET /products
router.get('/', (req, res) => {
  res.json(products);
});

// POST /products
router.post('/', (req: Request<{}, {}, Omit<Product, 'id'>>, res) => {
  const newProduct: Product = {
    ...req.body,
    id: Math.random().toString(36).substring(2)
  };
  products.push(newProduct);
  res.status(201).json(newProduct);
});

// GET /products/:id
router.get('/:id', (req: Request<{ id: string }>, res) => {
  const product = products.find(p => p.id === req.params.id);
  if (!product) return res.status(404).json({ error: 'Not found' });
  res.json(product);
});
        

GraphQL API with Type Safety

typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
          import { ApolloServer } from 'apollo-server';
import { gql } from 'graphql-tag';

interface Book {
  id: string;
  title: string;
  author: string;
}

const books: Book[] = [
  { id: '1', title: 'TypeScript Basics', author: 'John Doe' },
  { id: '2', title: 'Advanced Node.js', author: 'Jane Smith' }
];

const typeDefs = gql`
  type Book {
    id: ID!
    title: String!
    author: String!
  }

  type Query {
    books: [Book!]!
    book(id: ID!): Book
  }
`;

const resolvers = {
  Query: {
    books: (): Book[] => books,
    book: (_: any, args: { id: string }): Book | undefined => 
      books.find(book => book.id === args.id)
  }
};

const server = new ApolloServer({ typeDefs, resolvers });

server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});
        

WebSocket API with Type Safety

typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
          import { WebSocketServer } from 'ws';

interface Message {
  type: 'chat' | 'notification';
  content: string;
  timestamp: Date;
  userId: string;
}

const wss = new WebSocketServer({ port: 8080 });

wss.on('connection', ws => {
  ws.on('message', (data: string) => {
    try {
      const message: Message = JSON.parse(data);
      
      // Validate message structure
      if (!message.type || !message.content) {
        throw new Error('Invalid message format');
      }

      // Broadcast to all clients
      wss.clients.forEach(client => {
        client.send(JSON.stringify({
          ...message,
          timestamp: new Date()
        }));
      });
    } catch (err) {
      ws.send(JSON.stringify({ error: 'Invalid message' }));
    }
  });
});
        

Advanced Patterns

1. Repository Pattern with Generics:

typescript
1
2
3
4
5
6
7
8
9
10
11
          interface Repository<T> {
  getAll(): Promise<T[]>;
  getById(id: string): Promise<T | null>;
  create(item: Omit<T, 'id'>): Promise<T>;
  update(id: string, item: Partial<T>): Promise<T | null>;
  delete(id: string): Promise<boolean>;
}

class MongoUserRepository implements Repository<User> {
  // Implementation using MongoDB driver
}
        

2. Middleware Typing:

typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
          interface AuthenticatedRequest extends Request {
  user: {
    id: string;
    role: 'admin' | 'user';
  };
}

const authMiddleware = (
  req: Request, 
  res: Response, 
  next: NextFunction
) => {
  // Authentication logic
  const authenticatedReq = req as AuthenticatedRequest;
  authenticatedReq.user = { id: '123', role: 'user' };
  next();
};

app.get('/profile', authMiddleware, (req: AuthenticatedRequest, res) => {
  res.json({ user: req.user });
});
        

3. Configuration Typing:

typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
          interface AppConfig {
  port: number;
  db: {
    host: string;
    port: number;
    name: string;
  };
  auth: {
    secret: string;
    expiresIn: string;
  };
}

const config: AppConfig = {
  port: parseInt(process.env.PORT || '3000'),
  db: {
    host: process.env.DB_HOST || 'localhost',
    port: parseInt(process.env.DB_PORT || '27017'),
    name: process.env.DB_NAME || 'mydb'
  },
  auth: {
    secret: process.env.AUTH_SECRET || 'defaultsecret',
    expiresIn: process.env.AUTH_EXPIRES || '1h'
  }
};
        

By leveraging TypeScript in your Node.js projects, you'll benefit from:

  • Early detection of potential runtime errors
  • Better code documentation through types
  • Improved developer experience with autocompletion
  • Easier refactoring of large codebases
  • More reliable API contracts between frontend and backend

The combination of Node.js and TypeScript creates a powerful environment for building scalable, maintainable server-side applications that stand the test of time.