Use plain functions and modules when there is no state to protect and nothing to swap. Use a class when it keeps state and rules together, or when your framework expects one.
Functions and modules are better for:
- Stateless helpers:
slugify,formatMoney,parseCsvRow. A class with only static methods just adds a name. - One shared instance: a module is loaded once and cached. So
export const pool = new Pool()is already a singleton. - Private state: a closure hides variables, and nothing can get around it. A destructured function still works, because closures don't use
this. - Small handlers: Express middleware and Lambda handlers are plain functions.
Classes earn their place when:
- You make many instances that each hold state and rules:
Order,Money, a connection with its own buffer. - You need several implementations of one contract: storage adapters, payment gateways.
- The framework is built around them: NestJS providers, TypeORM entities, class-validator DTOs.
Don't mix styles randomly. Follow the codebase's convention.
- Stateless helpers are simpler as exported functions.
- A Node module is cached, so a module-level instance is already a singleton.
- Closures give truly private state and don't depend on
this. - Classes fit objects with state and rules, many instances, or several implementations.
- Frameworks like NestJS and TypeORM expect classes, so follow the framework.