
Express + TypeScript: architecture with classes or functions? Which one scales best in large projects?
Introduction:
I'm starting an API using:
- Express
- Prisma
- PostgreSQL
- TypeScript
The application will be relatively large, with multiple features and relationships between tables. My goal is to keep:
- readability
- scalability
- organization
- performance
- maintainability
I'm using a feature-based folder/module structure, separating each domain into its own folder, similar to the structure shown in the image.
Explanation:
My main question is about how to organize functions inside files (controller, service, repository, etc.) in a larger project.
I've looked into a few approaches and I'm unsure which direction to follow.
Simplified Examples:
OPTION 1 — Separate functions (arrow/functions)
Example:
customers.controller.ts
export const listCustomers = async () => {
// ...
};
export const createCustomer = async () => {
// ...
};
customers.routes.ts
import { Router } from "express";
import * as customersController from "./customers.controller";
const customersRouter = Router();
customersRouter.get("/", customersController.listCustomers);
This approach works well and is simple. One downside is that in larger projects, having many exported functions in a single file can make it feel less organized over time.
OPTION 2 — Classes
Example:
customers.controller.ts
export class CustomersController {
listCustomers = async () => {
// ...
};
createCustomer = async () => {
// ...
};
}
customers.routes.ts
import { Router } from "express";
import { CustomersController } from "./customers.controller";
const customersRouter = Router();
const customersController = new CustomersController();
customersRouter.get("/", customersController.listCustomers);
This approach helps group responsibilities within a single context, making it easier to see everything related to a specific module. It also tends to scale better as the project grows.
My real concern here is more about class behavior in real Node/Express projects: in some cases, when methods rely on instance context (this), it can be easy to lose that context depending on how functions are passed around or used. In the example above, this is avoided by using arrow functions, but I'm still unsure if using classes is actually a good architectural choice for this stack, or if it's just an unnecessary abstraction mainly for organization.
MAIN QUESTION:
For a relatively large Express + Prisma application, which approach scales better in terms of maintainability and structure?
Is it worth using classes just for grouping responsibilities, or is the functional approach (separate functions) generally simpler and more consistent in real-world Express projects?
- separate functions
- classes
- other better approaches?
I'd also like to understand:
- what people actually use in real production projects
- what problems usually appear as the project grows
- what should be avoided early in the project
Sorry for my English, I'm not a native speaker