Design Patterns in JavaScript — Part 1: Singleton & Module (With Real Examples)
Sachin Kasana

Sachin Kasana @sachinkasana

About: Principal Engineer by day, DevTools builder by night 🌙 | Love working with Node.js, React & AI | Sharing insights on clean code, performance, and web development. https://json-formatter-dev.vercel.ap

Location:
india
Joined:
Jun 30, 2022

Design Patterns in JavaScript — Part 1: Singleton & Module (With Real Examples)

Publish Date: Apr 27
0 0

🔁 Design Patterns in JavaScript — Part 1: Singleton & Module (With Real Examples)

Learn how Singleton and Module patterns work in JavaScript using real-world scenarios like database connections and logging utilities. This post helps you write reusable, scalable code using time-tested design patterns.

You’ve seen this before…

const db = connectToDatabase();
Enter fullscreen mode Exit fullscreen mode

Cool. But what if this line runs every time a function is called?

What if your app creates multiple DB connections because someone forgot to reuse an instance?

That’s where the Singleton and Module Pattern step in — to keep your code DRY, consistent, and efficient.

Let’s break it down — simply, with real examples you’ve probably already written (just without realizing it).

🧠 Why You Need These Patterns

JavaScript doesn’t have traditional class-based access control like Java or C#.

But that doesn’t mean you can’t enforce one-time instantiation , encapsulation , and controlled exports.

These patterns are perfect for:

  • Logging systems
  • Database connections
  • Global configs
  • Analytics or monitoring tools
  • Shared utilities (like token managers, feature toggles, etc.)

☝️ Pattern 1: The Singleton

✅ The Goal:

“Only one instance — no matter how many times I try.”

🛠 Real-World Example: DB Connection Manager

Let’s say you’re using MongoDB in a Node.js app:

 //db.js
let dbInstance;

export async function getDB() {
  if (dbInstance) return dbInstance;

  const client = await mongoClient.connect();
  dbInstance = client.db('my-app');
  return dbInstance;
}
Enter fullscreen mode Exit fullscreen mode

Now in any file:

import { getDB } from './db.js';

const db = await getDB(); // always the same instance
Enter fullscreen mode Exit fullscreen mode

💡 Why it works:

  • JS modules are cached after first import
  • The outer variable dbInstance is preserved across imports

✅ Problem solved: No more redundant DB connections

📦 Pattern 2: The Module Pattern

This is even more common — you’ve definitely used it when organizing utilities or configs.

// logger.js
const Logger = (() => {
  let level = 'info';

  const log = (msg) => {
    if (level !== 'silent') console.log(`[${level}]`, msg);
  };

  return {
    setLevel: (newLevel) => (level = newLevel),
    info: (msg) => log(msg),
  };
})();

export default Logger;
Enter fullscreen mode Exit fullscreen mode

Usage:

import Logger from './logger.js';

Logger.info('App started'); // [info] App started
Logger.setLevel('silent');
Logger.info('Won’t show'); // (nothing printed)
Enter fullscreen mode Exit fullscreen mode

✅ Private internal state

✅ Exposed public API

✅ No class needed

🧪 Real Project Use Cases

Use cases of design pattern

❓ Common Questions

“Why not just use a global?”

Because:

  • Globals leak across test environments
  • Are harder to control
  • Don’t allow private state
  • Break in SSR or multi-tenant setups

design pattern use cases

🚀 Up Next: The Factory Pattern

In the next post, we’ll cover how to use the Factory Pattern to dynamically create:

  • UI components
  • API handler classes
  • Strategy-based services (like payment gateways or feature flags)

This is where your real-world code starts to look beautifully abstract — but clean.

👉 Follow me on Medium or bookmark this series — we’re going full architecture mode (but JavaScript style).

Thank you for being a part of the community

Before you go:


Comments 0 total

    Add comment