Skip to content

Repository files navigation

Design and Software Architecture Patterns in TypeScript

A collection of 94 software design, architecture, concurrency, functional, and distributed systems patterns implemented in TypeScript.

wakatime

The goal of this repository is to provide simple and (hopefully) readable examples of different design patterns and their implementations. Each is implemented as a standalone TypeScript file with no dependencies (except for concurrency patterns, which use node:worker_threads to achieve parallelism and node:path for spawning the worker). The worker files in the concurrency folder are not runnable directly, they have to be started by their respective main file (i.e. they have to be a worker).

This repository is meant for educational purposes and the provided examples are NOT production-ready code. In some of the files I included information on what a production-ready implementation would include, but it does not mean that patterns that don't have these disclaimers can be copied and pasted into your production codebases. They are intended to be read and understood so you can apply them intentionally in your next project. All examples simplify the infrastructure as much as possible, focusing only on the actual pattern.

All of the files contain lots of comments which can hopefully help you understand why these patterns are designed that way. If you want to let the code speak for itself without me talking over it, you can view the no-comments branch.

Feedback

I am always open to all forms of feedback! If you spot a mistake or a bug in any of the examples, or you simply want to contribute, feel free to open issues or pull requests.

Running examples

Since every file is self-contained, you can simply run it directly using tools like ts-node:

npx ts-node behavioral/visitor.ts

or compile the project and run the generated JavaScript files:

tsc
node dist/concurrency/mutex.js

Note: The RAII example requires explicit resource management, which is currently a Stage 3 proposal. Make sure that your Node version supports it and you're on TypeScript 5.2+.

Patterns covered

Here is a list of all covered patterns. It also has more formal descriptions of each of the patterns, similar to what you may find in literature (some are shamelessly ripped from the Wikipedia article about Software design patterns).

Creational patterns

Name Description File
Abstract Factory Creates families of related objects without specifying concrete classes abstractFactory.ts
Builder Separates the construction of objects from their representation builder.ts
Dependency Injection Provides dependencies from outside instead of creating them internally dependencyInjection.ts
Dependency Injection Container Manages object creation and automatically resolves dependencies using a centralized container dependencyInjectionContainer.ts
Faceted Builder Splits a builder into multiple smaller builders facetedBuilder.ts
Factory Encapsulates object creation in a function or a class factory.ts
Factory Method Defines an interface for creating objects while allowing subclasses to decide the type factoryMethod.ts
Fluent Builder Connects the Builder and Fluent Interface patterns fluentBuilder.ts
Lazy Initialization Delays object creation until it's actually needed lazyInitialization.ts
Multiton Maintains multiple selectable instances of a class multiton.ts
Object Pool Reuses expensive objects instead of recreating them objectPool.ts
Parameter Object Bundles parameters into a single object to simplify method signatures parameterObject.ts
Prototype Creates objects by cloning existing instances prototype.ts
RAII (Resource Acquisition is Initialization) Ties resource management to object lifecycle. Since JavaScript uses a Garbage Collector, this is not true RAII and instead uses the using keyword raii.ts
Service Locator Provides a central registry for retrieving services serviceLocator.ts
Singleton Ensures a class has only one instance singleton.ts

Structural patterns

Name Description File
Adapter Allows incompatible interfaces to work together adapter.ts
Authority Inversion Moves authority over children from parent to a shared authority authorityInversion.ts
Bridge Separates abstraction from implementation bridge.ts
Composite Treats individual objects and compositions of objects uniformly composite.ts
Decorator Dynamically adds behavior to objects decorator.ts
Delegation Passes responsibility to a different object delegation.ts
Effectivity Stores effectivity ranges to allow client to check whether object was in effect at a given date effectivity.ts
Extension Object Adds new functionality without modifying existing objects extensionObject.ts
Facade Provides a simplified interface to a complex system facade.ts
Flyweight Reduces memory usage by sharing common state flyweight.ts
Front Controller Centralizes request handling frontController.ts
Marker Uses empty interfaces (or, in this case, classes) to attach metadata marker.ts
Mixin Adds reusable behavior to a class through functions that extend a base class, allowing a form of multiple inheritance. Credit: MusicMakerOwO mixin.ts
Module Groups several related elements into a single entity. This specific implementation is a revealing module, which exposes some of these elements publicly module.ts
Negative Cache (Negative Space) Caches non-existent objects to avoid expensive queries negativeCache.ts
Proxy Controls access to another object proxy.ts
Registry Provides centralized object lookup registry.ts
Repository Mediates between domain and data layers by exposing a collection-like interface repository.ts
Temporal Collection Stores temporal objects in a continuous timeline collection, keyed by milestones temporalCollection.ts
Twin Uses paired objects to extend functionality. This pattern also allows multiple inheritance in languages that do not support it twin.ts

Behavioral patterns

Name Description File
Audit Log Tracks read-only time-stamped actions for moderative purposes auditLog.ts
Blackboard Allows multiple agents to collaborate through shared knowledge blackboard.ts
Chain-of-responsibility Passes requests through a chain of handlers chainOfResponsibility.ts
Circuit Breaker Temporarily blocks requests to failing services to prevent cascading failures circuitBreaker.ts
Command Encapsulates requests as objects command.ts
Fluent Interface Provides method chaining APIs fluentInterface.ts
Intent Queue Centralizes action processing via unified intents intentQueue.ts
Interpreter Defines a representation for language grammar. In this case implemented as an expression tree, skipping tokenizing and parsing interpreter.ts
Iterator Provides a way to access elements of an aggregate without exposing the internal representation. In this case implemented using JavaScript iterators iterator.ts
Mediator Centralizes communication between objects mediator.ts
Memento Captures and restores object state memento.ts
Middleware Passes requests through a chain of composable handlers middleware.ts
Null Object Provides a default object instead of relying on null checks nullObject.ts
Observer Defines a one-to-many dependency where objects are notified of state changes observer.ts
Pipeline Passes values through multiple stages, each transforming the output of the previous one pipeline.ts
Retry Automatically re-attempts failed requests based on a specified policy retry.ts
Scheduler Controls the execution of tasks over time scheduler.ts
Servant Provides a common functionality for a group of objects servant.ts
Specification Encapsulates business rules as reusable specifications with Boolean algebra specification.ts
State Changes behavior when internal state changes state.ts
Strategy Defines interchangeable algorithms strategy.ts
Template Method Defines an algorithm skeleton while allowing steps to vary templateMethod.ts
Unit of Work Groups operations into a single unit that can be rolled back and committed unitOfWork.ts
Visitor Separates operations from object structures visitor.ts

Functional programming techniques

Name Description File
Composition Combines multiple functions into a single function composition.ts
Currying Transforms a function with multiple parameters into a sequence of single-parameter functions currying.ts
Either Represents a value that is one of two types either.ts
Lens Allows getting and setting values within immutable nested data structures in a composable way lens.ts
Memoization Caches results of expensive functions to prevent redundant recalculation memoization.ts
Option Represents a value that may or may not be present option.ts
Partial Apply (papply) Applies some arguments ahead of time, producing a function with fewer parameters partialApply.ts
Result Represents a value as either a success or a failure; a special case of Either result.ts
State monad Passes state through a series of computations in a purely functional way. Not to be confused with the behavioral State: this is an implementation of the State monad, whereas the behavioral State refers to a system where the behavior is changed along with the state state.ts

Messaging patterns

Name Description File
Choreography Saga Coordinates a transaction through events passed between services with no central coordinator. Choreography and Orchestration Sagas are often regarded as the same pattern, just two different implementations, but I chose to include them as separate patterns to show the differences between them choreographySaga.ts
CQRS (Command-Query Responsibility Segregation) Separates read and write into separate models cqrs.ts
Dead Letter Queue Stores undeliverable messages so they can be inspected later deadLetterQueue.ts
Event Bus (Pub/Sub) Lets decoupled services publish and subscribe to events eventBus.ts
Event Sourcing Stores state changes as a sequence of events eventSourcing.ts
Event Store Provides bitemporal storage for events eventStore.ts
Inbox Stores processed messages to achieve idempotency inbox.ts
Message Translator Translates incompatible message contracts messageTranslator.ts
Orchestration Saga Coordinates a transaction through events and commands dispatched by a central coordinator orchestrationSaga.ts
Outbox Ensures reliable delivery by storing messages in the same transaction as the data change outbox.ts

Concurrency patterns

Name Description File
Active Object Decouples method execution from method invocation activeObject.ts
Actor Model Encapsulates state and behavior in isolated actors communicating only through messages actorModel.ts
Balking Ignores actions if the object is in a state that does not allow it to perform them balking.ts
Guarded Suspension Delays actions until the object is able to perform them guardedSuspension.ts
Mutex Ensures only one thread can access a shared resource at a time. This implementation does not guarantee fairness mutex.ts
Read-Write Lock (RWLock) Allows concurrent reads but requires exclusive access for writes. This implementation does not guarantee fairness readWriteLock.ts
Semaphore Limits the number of concurrent accesses to a resource. This implementation does not guarantee fairness semaphore.ts
Thread pool Reuses a fixed set of threads to perform tasks threadPool.ts

Testing patterns

Name Description File
Fake Provides a working but simplified implementation for testing fake.ts
Mock Verifies the expected interactions occurred during a test mock.ts
Object Mother Provides reusable test object creation methods objectMother.ts
Spy Records information about calls for later verification spy.ts
Stub Returns predefined responses to calls stub.ts
Test Data Builder Constructs complex data structures incrementally testDataBuilder.ts
Test Object Supplies a preconfigured object for reuse across tests testObject.ts

About

A collection of design and software architecture patterns in TypeScript

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Contributors

Languages