Skip to main content
Practice Problems

What are the most common design patterns in Node.js?

Design Patterns in Node.js

Design patterns are reusable solutions to common programming problems. Node.js has several patterns that are especially relevant due to its asynchronous, event-driven nature.


1. Module Pattern

Node.js modules provide natural encapsulation:

js
// counter.js — encapsulated state let count = 0; module.exports = { increment: () => ++count, decrement: () => --count, getCount: () => count };
js
const counter = require('./counter'); counter.increment(); console.log(counter.getCount()); // 1

2. Singleton Pattern

Because Node.js caches require(), modules are naturally singletons:

js
// database.js class Database { constructor() { if (Database.instance) return Database.instance; this.connection = null; Database.instance = this; } async connect(url) { if (!this.connection) { this.connection = await createConnection(url); } return this.connection; } } module.exports = new Database();
js
// Both return the same instance const db1 = require('./database'); const db2 = require('./database'); console.log(db1 === db2); // true

3. Observer Pattern (EventEmitter)

The backbone of Node.js — used extensively in streams, HTTP, and custom events:

js
const EventEmitter = require('events'); class OrderService extends EventEmitter { placeOrder(order) { // Business logic... this.emit('orderPlaced', order); } } const orderService = new OrderService(); // Subscribers orderService.on('orderPlaced', (order) => { emailService.sendConfirmation(order); }); orderService.on('orderPlaced', (order) => { inventoryService.updateStock(order); }); orderService.placeOrder({ id: 1, item: 'Laptop' });

4. Factory Pattern

Creating objects without exposing the creation logic:

js
class DatabaseFactory { static create(type) { switch (type) { case 'postgres': return new PostgresAdapter(); case 'mongodb': return new MongoAdapter(); case 'redis': return new RedisAdapter(); default: throw new Error(`Unknown database type: ${type}`); } } } const db = DatabaseFactory.create(process.env.DB_TYPE);

5. Middleware Pattern (Chain of Responsibility)

Core pattern in Express.js and NestJS:

js
class MiddlewareChain { constructor() { this.middlewares = []; } use(fn) { this.middlewares.push(fn); return this; } async execute(context) { let index = 0; const next = async () => { if (index < this.middlewares.length) { const middleware = this.middlewares[index++]; await middleware(context, next); } }; await next(); } } const chain = new MiddlewareChain(); chain.use(async (ctx, next) => { console.log('Auth check'); ctx.user = { id: 1 }; await next(); }); chain.use(async (ctx, next) => { console.log(`User: ${ctx.user.id}`); await next(); });

6. Strategy Pattern

Swappable algorithms at runtime:

js
class PaymentProcessor { constructor(strategy) { this.strategy = strategy; } async pay(amount) { return this.strategy.process(amount); } } const stripeStrategy = { process: (amount) => stripe.charges.create({ amount }) }; const paypalStrategy = { process: (amount) => paypal.payment.create({ amount }) }; // Use Stripe const processor = new PaymentProcessor(stripeStrategy); await processor.pay(100);

7. Repository Pattern

Abstracts data access from business logic:

js
class UserRepository { constructor(database) { this.db = database; } async findById(id) { return this.db.query('SELECT * FROM users WHERE id = $1', [id]); } async create(userData) { return this.db.query( 'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *', [userData.name, userData.email] ); } async findByEmail(email) { return this.db.query('SELECT * FROM users WHERE email = $1', [email]); } }

Pattern Summary

PatternUse Case in Node.js
ModuleEncapsulation, code organization
SingletonDB connections, config, caches
ObserverEvent-driven communication
FactoryObject creation abstraction
MiddlewareRequest processing pipelines
StrategySwappable algorithms
RepositoryData access abstraction
DecoratorExtending behavior (NestJS)
ProxyCaching, logging, access control

Tip: Don't force patterns where they don't fit. Node.js's module system and event-driven nature already encourage clean patterns. Use them when they solve real problems, not for theoretical purity.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?
Practice Problems