TypeScript with Node.js
Using TypeScript for type-safe Node.js applications
Challenge
Section titled “Challenge”Leverage TypeScript for better error detection and code maintainability in Node.js projects.
Explanation
Section titled “Explanation”TypeScript adds static typing to JavaScript, catching errors at compile-time and improving developer experience.
npm init -ynpm install typescript @types/node --save-devnpm install expressnpm install --save-dev @types/express
# Initialize TypeScriptnpx tsc --init
# Watch modenpx tsc --watch
# Build and runnpm run buildnode dist/index.jsBasic Types
Section titled “Basic Types”// Primitivesconst name: string = 'John';const age: number = 30;const isActive: boolean = true;
// Union typeslet status: 'active' | 'inactive' = 'active';
// Array typesconst numbers: number[] = [1, 2, 3];const mixed: (string | number)[] = ['a', 1, 'b'];
// Tupleconst tuple: [string, number] = ['John', 30];
// Any and Unknownlet anything: any = 'can be anything';let unknown_value: unknown = 'must check type';
if (typeof unknown_value === 'string') { console.log(unknown_value.toUpperCase());}Interfaces and Types
Section titled “Interfaces and Types”// Interfaceinterface User { id: number; name: string; email: string; active?: boolean; // Optional}
// Type aliastype UserID = string | number;
// Function typesinterface GetUser { (id: number): Promise<User>;}
const getUser: GetUser = async (id) => { return { id, name: 'John', email: 'john@example.com' };};
// Extendinginterface Admin extends User { role: 'admin' | 'moderator';}Classes
Section titled “Classes”class Database { private connection: Connection; protected poolSize: number = 10; public readonly name: string;
constructor(name: string) { this.name = name; }
public connect(): void { this.connection = new Connection(); }
private createPool(): void { // Private method }
static getInstance(): Database { return new Database('db'); }}
// Abstract classabstract class Animal { abstract sound(): void;
move(): void { console.log('Moving...'); }}
class Dog extends Animal { sound(): void { console.log('Woof!'); }}Express with TypeScript
Section titled “Express with TypeScript”import express, { Express, Request, Response } from 'express';
const app: Express = express();
app.use(express.json());
interface UserRequest extends Request { user?: { id: number; email: string };}
// Middleware with typesconst authenticateToken = ( req: UserRequest, res: Response, next: Function): void => { const token = req.headers['authorization']; if (!token) { res.status(401).json({ error: 'No token' }); return; }
// Verify token... req.user = { id: 1, email: 'user@example.com' }; next();};
// Typed routesapp.get('/users/:id', (req: Request, res: Response): void => { const { id } = req.params; res.json({ id, name: 'John' });});
app.post('/users', (req: Request<{}, {}, User>, res: Response<User>): void => { const user: User = req.body; res.json(user);});
app.get('/protected', authenticateToken, (req: UserRequest, res: Response): void => { res.json({ user: req.user });});
app.listen(3000, (): void => { console.log('Server running on port 3000');});Generics
Section titled “Generics”// Generic functionfunction getProperty<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key];}
// Generic classclass Queue<T> { private items: T[] = [];
enqueue(item: T): void { this.items.push(item); }
dequeue(): T | undefined { return this.items.shift(); }}
// Usageconst numberQueue = new Queue<number>();numberQueue.enqueue(5);
const stringQueue = new Queue<string>();stringQueue.enqueue('hello');Decorators
Section titled “Decorators”// Enable in tsconfig.json: "experimentalDecoators": true
function Route(method: string, path: string) { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { descriptor.value._route = { method, path }; return descriptor; };}
class UserController { @Route('GET', '/users') listUsers(req: Request, res: Response): void { res.json([]); }}
// Class decoratorfunction Controller(path: string) { return function <T extends { new(...args: any[]): {} }>(constructor: T) { return class extends constructor { basePath = path; }; };}
@Controller('/api')class ProductController { // Routes prefixed with /api}enum Status { Active = 'active', Inactive = 'inactive', Pending = 'pending'}
interface User { id: number; status: Status;}
const user: User = { id: 1, status: Status.Active};
enum Level { Info = 0, Warning = 1, Error = 2}
const logLevel: Level = Level.Info;Common Pitfalls
Section titled “Common Pitfalls”// ❌ Using anyconst data: any = fetchData(); // Loses type checking
// ✅ Use unknownconst data: unknown = fetchData();if (typeof data === 'object') { // Now can access properties safely}
// ❌ Over-use of interfaces// ✅ Simple objects as type literalstype Config = { host: string; port: number };
// ❌ Mutable typesinterface Point { x: number; y: number;}
// ✅ Consider readonlyinterface ReadonlyPoint { readonly x: number; readonly y: number;}