Large Codebase Structure
Why structure matters in large TypeScript projects
As a TypeScript codebase grows, complexity increases in both logic and types. Without a clear structure, types become duplicated, dependencies become tangled, and refactoring becomes risky. A well-designed folder and module structure keeps both runtime logic and type relationships predictable.
Organize by domain, not by file type
Instead of separating files by technical concern (models, controllers, utils), prefer grouping by domain feature. This keeps related logic and types close together and reduces accidental cross-module coupling.
src/
users/
user.types.ts
user.service.ts
user.controller.ts
orders/
order.types.ts
order.service.ts
order.controller.ts
Avoid a global types folder
A common mistake is creating a single shared types directory that becomes a dumping ground. Over time, this leads to unclear ownership and circular dependencies. Types should live close to the logic that owns them.
Enforce directional dependencies
Define clear dependency rules. Domain should not depend on infrastructure. UI should not import database types. Establish one-directional flows to prevent tight coupling.
Public API per module
Each folder or module should expose a small public surface. Use an index file to control what is exported.
// users/index.ts
export { createUser } from "./user.service";
export type { User } from "./user.types";
This prevents accidental leakage of internal types.
Prevent circular dependencies
Circular imports are common in large systems. Keep modules independent and avoid mutual type references across domains. When necessary, extract shared concepts into a clearly owned shared module.
Separate domain and transport models
Do not let HTTP request or database schema shapes spread into business logic. Convert transport types into domain types at the boundary layer.
Use path aliases for clarity
Path aliases reduce deep relative imports and make module ownership clearer.
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@users/*": ["users/*"],
"@orders/*": ["orders/*"]
}
}
}
Scale through consistency
In large teams, consistency matters more than individual preference. Agree on naming conventions, file suffixes, and module export patterns. Predictability reduces onboarding time and architectural drift.
Common structural mistakes
- Single shared types folder with no ownership.
- Domain importing infrastructure-specific types.
- Uncontrolled re-exports causing hidden dependencies.
- Deep nested folder hierarchies with unclear boundaries.
Production guidance
- Group by domain or feature.
- Keep types close to their owners.
- Expose minimal public APIs.
- Enforce one-directional dependency flow.
- Document architectural rules in the repository.
What’s next
Next, we will focus on domain modeling with types and how to reflect real business rules directly in your TypeScript type system.