articles
4 min read
15 days ago

Design Patterns with Node.js

This article covers essential Node.js design patterns with real-world applications. From the Module patterns, Singleton patterns and Observer patterns, you'll see how these concepts can optimize code organization, resource management, and event handling. By incorporating these patterns, you can create more scalable, efficient, and maintainable Node.js applications.

Design Patterns with Node.js
Share this content:

 

The Patterns


1. Module Pattern

In large Node.js applications, you need to split the code into smaller, maintainable modules. The Module Pattern helps encapsulate functionality and avoid polluting the global namespace. This pattern is used to create reusable services like authentication, logging, and database connections.

Example: Authentication Module


// authModule.ts
export const AuthModule = (() => {
  const users = [{ username: "admin", password: "secret" }];

  const login = (username: string, password: string): string => {
    return users.find(user => user.username === username && user.password === password)
      ? "Login successful"
      : "Invalid credentials";
  };

  const logout = (): string => {
    return "User logged out";
  };

  return {
    login,
    logout,
  };
})();

// index.ts
import { AuthModule } from "./authModule";

console.log(AuthModule.login("admin", "secret")); // Login successful

Use Case: This pattern can be used for user authentication, session management, etc.

 

2. Singleton Pattern

The Singleton Pattern is used when you need a single instance of a class to be shared across an application. For example, in Node.js, a database connection pool should have a single instance to avoid creating multiple connections unnecessarily.

Example: Database Connection


// dbSingleton.ts
class Database {
  private static instance: Database;
  private connection: { status: string };

  private constructor() {
    this.connection = this.connect();
  }

  private connect(): { status: string } {
    console.log("Connecting to DB...");
    return { status: "connected" };
  }

  public static getInstance(): Database {
    if (!Database.instance)


// index.ts
import Database from "./dbSingleton";

const db1 = Database.getInstance();
const db2 = Database.getInstance();

console.log(db1.getConnection()); // { status: 'connected' }
console.log(db1 === db2);         // true


Use Case: You can use this to manage a single database connection instance across your app.

 

3. Observer Pattern ( I do like the name Listener Pattern as well)

The Observer Pattern is useful when you want to notify multiple objects of changes in one object. It’s often used for event-based architectures or Pub/Sub messaging systems, such as when sending real-time updates to connected users in a chat application.

Example: Event Emitter in Node.js


// eventObserver.ts
import { EventEmitter } from 'events';

class Chat extends EventEmitter {}

export const chat = new Chat();

// index.ts
import { chat } from "./eventObserver";

chat.on("message", (msg: string) => {
  console.log(`User1 received: ${msg}`);
});

chat.on("message", (msg: string) => {
  console.log(`User2 received: ${msg}`);
});

chat.emit("message", "Hello from TypeScript!");

/*
While this is a very raw example, imagine you would want to go Class Based, thiking about OOP, and it is what you should be aiming to, here below it follows:
*/

// ChatService.ts
import { EventEmitter } from 'events';

class ChatService extends EventEmitter {
  sendMessage(message: string): void {
    this.emit("message", message);
  }

  subscribe(listener: (msg: string) => void): void {
    this.on("message", listener);
  }
}

export default new ChatService(); // Shared instance

// index.ts
import ChatService from "./ChatService";

ChatService.subscribe((msg) => console.log("User1 got:", msg));
ChatService.subscribe((msg) => console.log("User2 got:", msg));

ChatService.sendMessage("Hello from class-based Observer!");

Use Case: Ideal for applications with real-time notifications like chat, stock updates, etc.

 

4. Middleware Pattern

In Node.js, middleware is widely used, especially in frameworks like Express. The Middleware Pattern allows you to build a chain of handlers to process requests and responses, providing flexibility for cross-cutting concerns like authentication, logging, or validation.

Example: Express Middleware for Logging and Authentication


// middlewareExample.ts
import express, { Request, Response, NextFunction } from 'express';

const app = express();

// Logger middleware
const logger = (req: Request, res: Response, next: NextFunction): void => {
  console.log(`${req.method} ${req.url}`);
  next();
};

// Auth middleware
const isAuthenticated = (req: Request, res: Response, next: NextFunction): void => {
  const auth = req.headers['authorization'];
  if (auth === 'Bearer token123') {
    next();
  } else {
    res.status(401).send("Unauthorized");
  }
};

app.use(logger);
app.use(isAuthenticated);

app.get('/secure', (req: Request, res: Response) => {
  res.send("This is a secure route");
});

app.listen(3000, () => console.log("Server running on http://localhost:3000"));


Use Case: Express applications heavily rely on middleware patterns to handle HTTP request processing.

 

Thanks for reading, hope this give you a glimpse of it and you are hungry to learn more about patterns and best practices in Software Development.

CTA Card Image
#exploringtech

Explore the world with me...

I'm navigating the worlds of Frontend, Backend, and DevOps—steering through projects while blending innovation with fundamentals. Join me on this journey to uncover fresh insights and level up your skills every step of the way.

me-icon-footer

Tech Explorer

A minimal portofolio and blog website, showing my expertise and spreading the rich content of programming.

#exploringtech
Currently At
Lisbon, PT
All rights reserverd | Copyright 2025