How do you architect an enterprise Express application using 3-layer Controller-Service-Repository patterns?
❓ Interview Prompt: Explain separation of concerns in Express: Routers/Controllers (HTTP layer), Service Layer (business logic), and Repository/DAO Layer (database access).
Putting database SQL queries and complex business logic directly inside Express route callbacks leads to unmaintainable spaghetti code. Enterprise applications enforce Separation of Concerns using a 3-Tier Layered Architecture:
1. Controller / Router Layer (The HTTP Interface):
Responsible ONLY for HTTP concerns: parsing 'req.body' / 'req.params', validating input syntax, calling the Service Layer, and sending 'res.status().json()'.
2. Service Layer (The Business Logic Engine):
Contains core domain rules, payment calculations, and workflow orchestrations.
Framework Agnostic: The service layer knows nothing about Express, 'req', or 'res'. It accepts plain JavaScript objects and returns Promises, making business logic 100% unit-testable!
3. Repository / Data Access Object (DAO) Layer:
Responsible ONLY for talking to databases (SQL, MongoDB, Redis) or external REST APIs.
Code Implementation Example
// 3. REPOSITORY LAYER (Database access only)
export class UserRepository {
async findByEmail(email: string) {
return await prisma.user.findUnique({ where: { email } });
}
}
// 2. SERVICE LAYER (Business logic — Framework Agnostic!)
export class UserService {
constructor(private userRepo: UserRepository) {}
async registerUser(data: CreateUserDTO) {
const existing = await this.userRepo.findByEmail(data.email);
if (existing) throw new ConflictError('Email registered');
return await this.userRepo.create(data);
}
}
// 1. CONTROLLER LAYER (HTTP concerns only — parses req, formats res)
export class UserController {
constructor(private userService: UserService) {}
register = async (req: Request, res: Response, next: NextFunction) => {
try {
const user = await this.userService.registerUser(req.body);
res.status(201).json({ success: true, data: user });
} catch (err) { next(err); }
};
}Isolate HTTP concerns in Controllers, domain logic in framework-agnostic Services, and database queries in Repositories.