Clean Architecture in TypeScript
Advertisement
Clean Architecture organizes code by layers. Each layer has clear responsibilities and dependencies flow inward.
Layers
src/
domain/ # Business logic
entities/
usecases/
repositories/
infrastructure/ # Frameworks, DBs
database/
http/
presentation/ # Controllers, routes
controllers/
routes/
Implementation
// Domain Entity
export interface User {
id: number;
email: string;
name: string;
}
// Repository Interface
export interface UserRepository {
save(user: User): Promise<User>;
findById(id: number): Promise<User | null>;
findByEmail(email: string): Promise<User | null>;
}
// Use Case
export class CreateUserUseCase {
constructor(private userRepository: UserRepository) {}
async execute(email: string, name: string): Promise<User> {
const existing = await this.userRepository.findByEmail(email);
if (existing) throw new Error("User exists");
const user: User = { id: Math.random(), email, name };
return this.userRepository.save(user);
}
}
// Infrastructure Implementation
export class PostgresUserRepository implements UserRepository {
async save(user: User): Promise<User> {
// Database code
return user;
}
async findById(id: number): Promise<User | null> {
// Database code
return null;
}
async findByEmail(email: string): Promise<User | null> {
// Database code
return null;
}
}
// Presentation (Controller)
export class UserController {
constructor(private createUserUseCase: CreateUserUseCase) {}
async create(req: any, res: any) {
const user = await this.createUserUseCase.execute(
req.body.email,
req.body.name
);
res.json(user);
}
}
Benefits
- Testable: Mock dependencies easily
- Maintainable: Clear separation of concerns
- Flexible: Swap implementations
- Independent: Frameworks don't dictate structure
FAQ
Q: Is Clean Architecture overkill? A: For small projects, yes. For large teams and complex logic, essential.
Clean Architecture scales with your application.
Advertisement