Boumlik BrahimBoumlik Brahim
Back to Journal
Software Design

Design Patterns

Master the essential design patterns (Creational, Structural, Behavioral) with real-world TypeScript examples. Learn when to use Singleton, Factory, Adapter, Strategy, and more.

Design patterns are typical solutions to common problems in software design. They are like blueprints that you can customize to solve a recurring design problem in your code. In this guide, we'll explore the three main categories of patterns—Creational, Structural, and Behavioral—with practical TypeScript examples tailored for the modern NestJS and React ecosystem.

1. Creational Patterns

These patterns provide object creation mechanisms that increase flexibility and reuse of existing code.

Singleton

Intent: Ensure a class has only one instance, while providing a global access point to this instance.

NestJS Context: In NestJS, almost every provider (Service, Repository) is a Singleton by default. The Dependency Injection (DI) container manages the single instance for you, so you rarely need to implement the pattern manually.


// The Manual Way (Classic Singleton)
class DatabaseConnection {
  private static instance: DatabaseConnection;
  private constructor() {} // Private prevents 'new'

  public static getInstance(): DatabaseConnection {
    if (!DatabaseConnection.instance) {
      DatabaseConnection.instance = new DatabaseConnection();
    }
    return DatabaseConnection.instance;
  }
}

// The NestJS Way (Better)
@Injectable() // The container manages the singleton lifecycle
export class DatabaseService {
  constructor(private readonly config: ConfigService) {}
}
    

Factory Method

Intent: Define an interface for creating an object, but let subclasses decide which class to instantiate.

Use Case: Creating different payment processors (Stripe, PayPal) based on user configuration.


abstract class PaymentAdapter {
  abstract process(amount: number): Promise;
}

class StripeAdapter extends PaymentAdapter {
  async process(amount: number) { /* ... */ }
}

class PayPalAdapter extends PaymentAdapter {
  async process(amount: number) { /* ... */ }
}

class PaymentFactory {
  static create(provider: 'stripe' | 'paypal'): PaymentAdapter {
    if (provider === 'stripe') return new StripeAdapter();
    if (provider === 'paypal') return new PayPalAdapter();
    throw new Error('Unknown provider');
  }
}
    

Builder

Intent: Construct complex objects step by step. In TypeScript, we can often achieve this cleanly with object literals or partials, but the fluent builder pattern is useful for complex validations.


// Functional Builder approach
class HttpRequestBuilder {
  private config: Partial = { method: 'GET' };

  setUrl(url: string) {
    this.config.url = url;
    return this;
  }

  setMethod(method: 'GET' | 'POST') {
    this.config.method = method;
    return this;
  }

  setBody(data: unknown) {
    this.config.body = JSON.stringify(data);
    return this;
  }

  build() {
    if (!this.config.url) throw new Error('URL is required');
    return this.config as RequestConfig;
  }
}

// Usage
const req = new HttpRequestBuilder()
  .setUrl('https://api.example.com')
  .setMethod('POST')
  .setBody({ id: 1 })
  .build();
    

2. Structural Patterns

These patterns explain how to assemble objects and classes into larger structures while keeping these structures flexible and efficient.

Adapter

Intent: Allows objects with incompatible interfaces to collaborate. Essential when integrating legacy code or third-party libraries.

Client  ──▶  Target Interface  ◀──  Adapter  ──▶  Adaptee (3rd Party)
             (processPayment)       (implements    (makeCharge)
                                     processPayment)

// External Library (Incompatible Interface)
class OldLogger {
  public writeLog(msg: string, severity: number) { /* ... */ }
}

// Our Application Interface
interface ILogger {
  info(message: string): void;
  error(message: string): void;
}

// Adapter
class LoggerAdapter implements ILogger {
  constructor(private oldLogger: OldLogger) {}

  info(message: string) {
    this.oldLogger.writeLog(message, 1);
  }

  error(message: string) {
    this.oldLogger.writeLog(message, 2);
  }
}
    

Decorator

Intent: Attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors. In TypeScript/NestJS, this is a core language feature.

Use Case: Logging execution time, validation, API endpoint definitions.


// Custom Decorator to measure execution time
function LogExecutionTime() {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;

    descriptor.value = async function (...args: any[]) {
      const start = performance.now();
      const result = await originalMethod.apply(this, args);
      const end = performance.now();
      console.log(`${propertyKey} took ${(end - start).toFixed(2)}ms`);
      return result;
    };
  };
}

class UserService {
  @LogExecutionTime()
  async findUser(id: string) {
    // Database call...
    return { id, name: 'John' };
  }
}
    

Facade

Intent: Provides a simplified interface to a library, a framework, or any other complex set of classes.

          ┌─────────────┐
          │   Facade    │ (Simple Interface)
          └──────┬──────┘
                 │
      ┌──────────┼──────────┐
      ▼          ▼          ▼
┌─────────┐  ┌─────────┐  ┌─────────┐
│ Video   │  │ Audio   │  │ Subtitles│
│ Decoder │  │ Decoder │  │ Service │
└─────────┘  └─────────┘  └─────────┘

class VideoConversionFacade {
  convert(file: string, format: string) {
    const video = new VideoFile(file);
    const audio = new AudioExtractor(video).extract();
    const result = new Muxer().mux(video, audio, format);
    return result;
  }
}
    

3. Behavioral Patterns

These patterns are concerned with algorithms and the assignment of responsibilities between objects.

Observer

Intent: Lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they're observing.

Modern Context: In the Angular/NestJS ecosystem, RxJS is the standard implementation of the Observer pattern.


import { Subject } from 'rxjs';

// The Subject is both an Observable and an Observer
const newsFeed$ = new Subject();

// Subscriber 1 (Observer)
newsFeed$.subscribe(news => console.log(`Consumer A received: ${news}`));

// Subscriber 2 (Observer)
newsFeed$.subscribe(news => console.log(`Consumer B received: ${news}`));

// Emitting an event
newsFeed$.next('Breaking News: TypeScript 6.0 Released!');
// Both consumers log the message immediately
    

Strategy

Intent: Lets you define a family of algorithms, put each of them into a separate class, and make their objects interchangeable.

NestJS Context: The Passport.js integration in NestJS is a textbook implementation of the Strategy pattern. You switch between 'jwt', 'local', or 'google' strategies without changing the authentication controller logic.


// The Strategy Interface is effectively defined by Passport
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: process.env.JWT_SECRET,
    });
  }

  async validate(payload: any) {
    return { userId: payload.sub, username: payload.username };
  }
}

// Context: The AuthGuard uses the strategy configured
@UseGuards(AuthGuard('jwt'))
@Get('profile')
getProfile(@Request() req) {
  return req.user;
}
    

Conclusion

Understanding these patterns transforms you from a coder who writes syntax to a software engineer who designs systems. By leveraging NestJS's built-in patterns (Singleton Services, Decorators, Strategies) and TypeScript's modern features, you can implement these timeless concepts with minimal boilerplate.

Boumlik BrahimBoumlik Brahim

Technical Lead & IT Architecture Expert. Architecting resilient cloud ecosystems and scalable, high-performance solutions.

boumlik01brahim@gmail.com

Explore

Connect

  • Boumlik-Brahim
  • brahim-boumlik
  • @BOUMLIKBRAHIM4
  • @brahimboumlik
  • Brahim Boumlik

Status

Location

Casablanca, Morocco

Download Resume

© 2026 Boumlik Brahim.

GitHubLinkedInTwitterInstagramFacebook