bugl
bugl
HomeLearnPatternsPathsSearchPremium
HomeLearnPatternsPaths

Loading lesson path

Learn/Node.js/JS & TS Features
Node.js•JS & TS Features

Node.js Advanced TypeScript

Advanced TypeScript for Node.js

This guide dives into advanced TypeScript features and patterns specifically useful for Node.js applications.

For comprehensive TypeScript documentation, visit TypeScript Tutorial .

Advanced Type System Features

TypeScript's type system provides powerful tools for creating robust and maintainable Node.js applications.

Union and Intersection Types

// Union type
function formatId(id: string | number) {
 return `ID: ${id}`;
}
// Intersection type
type User = { name: string } & { id: number };

Type Guards

type Fish = { swim: () => void };
type Bird = { fly: () => void };
function isFish(pet: Fish | Bird): pet is Fish {
 return 'swim' in pet;
}

Advanced Generics

// Generic function with constraints
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
 return obj[key];
}
// Generic interface with default type
interface PaginatedResponse<T = any> {
 data: T[];
 total: number;
 page: number;
 limit: number;
}
// Using generic types with async/await in Node.js
async function fetchData<T>(url: string): Promise<T> {
 const response = await fetch(url);
 return response.json();
}

Mapped and Conditional Types

// Mapped types
type ReadonlyUser = {
 readonly [K in keyof User]: User[K];
};
// Conditional types
type NonNullableUser = NonNullable<User | null | undefined>; // User
// Type inference with conditional types type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getUser() {
 return { id: 1, name: 'Alice' } as const;
}
type UserReturnType = GetReturnType<typeof getUser>; // { readonly id: 1; readonly name: "Alice"; }

Type Inference and Type Guards

TypeScript's type inference and type guards help create type-safe code with minimal annotations:

// Type inference with variables
const name = 'Alice'; // TypeScript infers type: string
const age = 30; // TypeScript infers type: number
const active = true; // TypeScript infers type: boolean
// Type inference with arrays
const numbers = [1, 2, 3]; // TypeScript infers type: number[]
const mixed = [1, 'two', true]; // TypeScript infers type: (string | number | boolean)[]
// Type inference with functions
function getUser() {
 return { id: 1, name: 'Alice' }; // Return type inferred as { id: number; name: string; }
}
const user = getUser(); // user inferred as { id: number; name: string; }
console.log(user.name); // Type checking works on inferred properties

Advanced TypeScript Patterns for Node.js

These patterns help build more maintainable and type-safe Node.js applications:

Advanced Decorators

// Parameter decorator with metadata
function validateParam(target: any, key: string, index: number) {
 const params = Reflect.getMetadata('design:paramtypes', target, key) || [];
 console.log(`Validating parameter ${index} of ${key} with type ${params[index]?.name}`);
}
// Method decorator with factory
function logExecutionTime(msThreshold = 0) {
 return function (target: any, key: string, descriptor: PropertyDescriptor) {
 const originalMethod = descriptor.value;
 descriptor.value = async function (...args: any[]) {
 const start = Date.now();
 const result = await originalMethod.apply(this, args);
 const duration = Date.now() - start;
 if (duration > msThreshold) {
 console.warn(`[Performance] ${key} took ${duration}ms`);
 }
 return result;
 };
 };
}
class ExampleService {
 @logExecutionTime(100)
 async fetchData(@validateParam url: string) {
 // Implementation
 }
}

Advanced Utility Types

// Built-in utility types with examples interface User {
id: number;
name: string;
email?: string;
createdAt: Date;
}
// Create a type with specific properties as required
type AtLeast<T, K extends keyof T> = Partial<T> & Pick<T, K>;
type UserCreateInput = AtLeast<User, 'name' | 'email'>; // Only name is required
// Create a type that makes specific properties required
WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
type UserWithEmail = WithRequired<User, 'email'>;
// Extract function return type as a type
type UserFromAPI = Awaited<ReturnType<typeof fetchUser>>;

Type-Safe Event Emitters

import { EventEmitter } from 'events';
type EventMap = {
 login: (userId: string) => void;
 logout: (userId: string, reason: string) => void;
 error: (error: Error) => void;
};
class TypedEventEmitter<T extends Record<string, (...args: any[]) => void>> {
 private emitter = new EventEmitter();
 on<K extends keyof T>(event: K, listener: T[K]): void {
 this.emitter.on(event as string, listener as any);
 }
 emit<K extends keyof T>(
 event: K,
 ...args: Parameters<T[K]>
): boolean {
 return this.emitter.emit(event as string, ...args);
}
}
// Usage
const userEvents = new TypedEventEmitter<EventMap>();
userEvents.on('login', (userId) => {
 console.log(`User ${userId} logged in`);
});
// TypeScript will show an error for incorrect argument types
// userEvents.emit('login', 123);
// Error: Argument of type 'number' is not assignable to 'string'

TypeScript Best Practices for Node.js

Key Takeaways

  • Leverage TypeScript's advanced type system for better code safety and developer experience
  • Use generics to create flexible and reusable components without losing type safety
  • Implement decorators for cross-cutting concerns like logging, validation, and performance monitoring
  • Utilize utility types to transform and manipulate types without code duplication
  • Create type-safe abstractions for Node.js-specific patterns like event emitters and streams

Performance Considerations

  • Be mindful of complex types that might impact compilation time
  • Use type over interface for complex type operations
  • Consider using as const for literal types when appropriate
  • Use unknown instead of any for type-safe dynamic typing

For comprehensive TypeScript documentation and examples, visit our TypeScript Tutorial .

Previous

Node.js TypeScript

Next

Node.js Linting & Formatting