TypeScript Chain of Responsibility Design Pattern

Ibrahim sengun
3 min readJan 19, 2024

--

What is chain of responsibility design pattern?

The Chain of Responsibility design pattern is a behavioral design pattern. It processes requests within a chain of handler objects, deciding whether to pass or deny the requests.

There are several terminologies in the chain of responsibility design pattern. These are:

  • Handler: It defines the properties for concrete handlers.
  • Concrete Handler: It contains the logic for the required properties defined in the handler, and it decides the fate of requests, whether to pass them or not.
  • Client: This refers to the application or function that communicates with the system.

When should the chain of responsibility design pattern be used?

The Chain of Responsibility design pattern can be used in areas where there is a need to process client requests repeatedly from different perspectives, such as authentication validation, etc. It is also applicable when you have a structure like a ladder, where each process in the objects is connected to one another and relies on the results of each other.

Another use of the Chain of Responsibility design pattern can be when the processing sequence or shape of requests is unknown beforehand and can change at runtime with each different type of request.

How to implement chain of responsibility design pattern in TypeScript

Let’s apply the Chain of Responsibility design pattern to TypeScript. First, let’s imagine a scenario where we are trying to build an authentication server for our application. Initially, we create a simple system that consists of just matching the credentials. However, as our application grows, we decide to add a few other properties to our authentication server, such as validation and two-factor authentication.

To effectively build this system, we first decide to split all the operations in the system, such as username checking and password checking, etc.

After splitting our operations according to the Single Responsibility Principle, we decide to create an effective order sequence for all the operations in the system. To achieve this, we connect each of the operations to each other like a chain, employing the Chain of Responsibility design pattern.

This way, when a request comes to the system, we reordered the sequence of our operations based on the request at runtime and let the handlers in the Chain of Responsibility design pattern do their work.

With this approach, when there is a need to reject the request or cut the process, we simply let the handlers decide. Instead of waiting for the entire operations to complete, we gain the ability to cut the processing where it is rejected, saving resources and time.

Chain of Responsibility desing pattern diagram

Chain of Responsibility desing pattern diagram

Chain of Responsibility desing pattern code

// Handler interface
interface AuthenticationHandler {
setNextHandler(handler: AuthenticationHandler): void;
handleRequest(credentials: any): boolean;
}

// Concrete Handler
class UsernameHandler implements AuthenticationHandler {
private nextHandler?: AuthenticationHandler;

setNextHandler(handler: AuthenticationHandler): void {
this.nextHandler = handler;
}

handleRequest(credentials: any): boolean {
if (credentials.username) {
console.log("Username is valid");
if (this.nextHandler) {
return this.nextHandler.handleRequest(credentials);
}
return true;
} else {
console.log("Invalid username.");
return false;
}
}
}

// Concrete Handler
class PasswordHandler implements AuthenticationHandler {
private nextHandler?: AuthenticationHandler;

setNextHandler(handler: AuthenticationHandler): void {
this.nextHandler = handler;
}

handleRequest(credentials: any): boolean {
if (credentials.password) {
console.log("Password is valid.");
if (this.nextHandler) {
return this.nextHandler.handleRequest(credentials);
}
return true;
} else {
console.log("Invalid password.");
return false;
}
}
}

// Concrete Handler
class TwoFactorHandler implements AuthenticationHandler {
private nextHandler?: AuthenticationHandler;

setNextHandler(handler: AuthenticationHandler): void {
this.nextHandler = handler;
}

handleRequest(credentials: any): boolean {
if (credentials.twoFactorCode) {
console.log("Two-factor authentication successful.");
return true;
} else {
console.log("Two-factor authentication code missing.");
return false;
}
}
}

// Client
const authenticationChain = new UsernameHandler();
const passwordHandler = new PasswordHandler();
const twoFactorHandler = new TwoFactorHandler();
authenticationChain.setNextHandler(passwordHandler);
passwordHandler.setNextHandler(twoFactorHandler);

const credentials = { username: "user123", password: "pass123", twoFactorCode: "123456" };
const result = authenticationChain.handleRequest(credentials);
console.log("Authentication Result 1:", result);
/*
Username is valid
Password is valid.
Two-factor authentication successful.
Authentication Result 1: true
*/

--

--