DEV Community

Lakshay Kalra
Lakshay Kalra

Posted on

🧩 Building a Pluggable Notification System in JavaScript Using the Factory Pattern

Sending notifications β€” via email, SMS, push, or Slack β€” is a common requirement in most applications. But how do you design such a system so that it's easy to extend without modifying existing logic every time a new channel is added?

Enter the Factory Pattern and Open/Closed Principle.

In this article, I’ll walk you through how to build a flexible notification system using modern JavaScript, following clean architecture principles.

🧱 The Problem

Most developers start with something like this:

if (type === 'EMAIL') {
  sendEmail(message);
} else if (type === 'SMS') {
  sendSms(message);
} else if (type === 'PUSH') {
  sendPush(message);
}

Enter fullscreen mode Exit fullscreen mode

This works β€” until your PM says, "We also need to support Slack. Oh, and WhatsApp next week."

Suddenly you're modifying the same block of logic again and again. That's not scalable.

βœ… The Goal

We want a system where:

Each notification type is its own class.

We can register new types without changing core logic.

The notification center can dynamically send messages based on type.

πŸ—οΈ Step-by-Step Implementation

  • Define Notification Classes
class EmailNotification {
  send(message) {
    console.log(`sending email notification with message ${message}`);
  }
}

class SmsNotification {
  send(message) {
    console.log(`sending sms notification with message ${message}`);
  }
}

class PushNotification {
  send(message) {
    console.log(`sending push notification with message ${message}`);
  }
}

class SlackNotification {
  send(message) {
    console.log(`sending slack notification with message ${message}`);
  }
}

Enter fullscreen mode Exit fullscreen mode

Each class follows the same interface: a send() method that takes a message.

  • Create a Factory Base Class
class NotificationCreator {
  constructor() {
    this.registry = {
      'EMAIL': EmailNotification,
      'SMS': SmsNotification,
      'PUSH': PushNotification,
    };
  }

  register(type, notificationClass) {
    if (!this.registry[type]) {
      this.registry[type] = notificationClass;
    }
  }

  createNotification(type) {
    const NotificationClass = this.registry[type];
    if (!NotificationClass) {
      throw new Error(`${type} class is not implemented`);
    }
    return new NotificationClass();
  }
}

Enter fullscreen mode Exit fullscreen mode

This class:

Holds a registry of notification types

Provides a register() method to add new types dynamically

Has a createNotification() method that acts as a factory

  • Notification Center That Sends Messages
class NotificationCenter extends NotificationCreator {
  send(message, type) {
    const notification = this.createNotification(type);
    notification.send(message);
  }
}

Enter fullscreen mode Exit fullscreen mode
  • Putting It All Together
const notification = new NotificationCenter();

notification.send("Hey Email", "EMAIL");
notification.send("hey SMS", "SMS");
notification.send("hey PUSH-Notification", "PUSH");

// Register Slack dynamically
notification.register("SLACK", SlackNotification);
notification.send("Hey Slack", "SLACK");

Enter fullscreen mode Exit fullscreen mode

🎯 Benefits of This Approach

βœ… Open/Closed Principle: You can add new notification types without modifying existing logic.
βœ… Scalable: Future types like WhatsApp, Telegram, Discord can be plugged in with one register() call.
βœ… Testable: Each class can be tested independently.
βœ… Clean Code: No repetitive if/else or switch blocks.

🧠 Bonus Tip: Make It Even Cleaner

Move the default registry into a method like registerDefaults() if your base class grows. Also, consider adding validation or interfaces if you're using TypeScript.

πŸš€ Final Thoughts

This pattern works great for:

Notification systems

Payment gateways (Stripe, Razorpay, PayPal)

Message formatters (Markdown, HTML, plain text)

By following this strategy, you're not only making your code cleaner β€” you're also writing software that's built to scale.

πŸ’¬ Got questions or improvements? Drop them in the comments β€” let’s chat clean code!

Tiugo image

Modular, Fast, and Built for Developers

CKEditor 5 gives you full control over your editing experience. A modular architecture means you get high performance, fewer re-renders and a setup that scales with your needs.

Start now

Top comments (0)

Neon image

Next.js applications: Set up a Neon project in seconds

If you're starting a new project, Neon has got your databases covered. No credit cards. No trials. No getting in your way.

Get started β†’

πŸ‘‹ Kindness is contagious

Engage with a wealth of insights in this thoughtful article, cherished by the supportive DEV Community. Coders of every background are encouraged to bring their perspectives and bolster our collective wisdom.

A sincere β€œthank you” often brightens someone’s dayβ€”share yours in the comments below!

On DEV, the act of sharing knowledge eases our journey and forges stronger community ties. Found value in this? A quick thank-you to the author can make a world of difference.

Okay