// db.ts
import { Pool } from "pg";
export const pool = new Pool({ connectionString: process.env.DATABASE_URL });
In Node, export an instance from a module, as shown above. Node runs a module's top-level code once and caches the result by its resolved file path. So every file that imports it gets the same object. You don't need the classic private constructor and static getInstance(). This suits a connection pool or a logger.
The downsides come from global state:
- Hidden dependencies. A function that imports
pooldoesn't show it in its signature. - Harder tests. You must mock the module instead of passing in a fake. State also leaks from one test to the next.
- Early creation. The object is created at import time, maybe before the config is ready.
The cache works per resolved path. So two copies of the same package in node_modules can create two instances.
A better design: create a single instance at the composition root (the one place where the app wires its objects together). Then pass it in through constructors. This is what a DI (dependency injection) container does by default.
- Node's module cache makes an exported instance a singleton.
- Singletons hide dependencies, share global mutable state and are hard to test.
- Creating the instance at import time can happen before the config is ready.
- Prefer one instance created at the composition root and passed in (injected).