🔁 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();
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;
}
Now in any file:
import { getDB } from './db.js';
const db = await getDB(); // always the same instance
💡 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;
Usage:
import Logger from './logger.js';
Logger.info('App started'); // [info] App started
Logger.setLevel('silent');
Logger.info('Won’t show'); // (nothing printed)
✅ Private internal state
✅ Exposed public API
✅ No class needed
🧪 Real Project Use Cases
❓ 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
🚀 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:
- Be sure to clap and follow the writer ️👏 ️️
- Follow us: X | LinkedIn | YouTube | Newsletter | Podcast | Differ | Twitch
- Check out CoFeed, the smart way to stay up-to-date with the latest in tech 🧪
- Start your own free AI-powered blog on Differ 🚀
- Join our content creators community on Discord 🧑🏻💻
- For more content, visit plainenglish.io + stackademic.com