Code Organization
In this page:
Organize by Responsibility
Grouping files by feature or responsibility (e.g. a users/ folder with its types, logic, and tests together) scales better than grouping by file type, since related changes stay co-located instead of scattered across types/, services/, and components/ folders.
Example: Organize by Responsibility
// users/types.ts, users/service.ts, users/user.test.ts
// grouped together rather than scattered across types/, services/, tests/
console.log("Feature-based folders keep related files co-located");
Use Named Exports
Named exports make it clear at the import site exactly what's being pulled in and make refactoring safer, since renaming a default export doesn't trigger any warning at its call sites the way renaming a named export does.
Example: Use Named Exports
export function formatDate(date: Date): string {
return date.toISOString();
}
export const APP_NAME = "MyApp";
console.log(formatDate(new Date()), APP_NAME);
Separate Types from Implementation When Useful
Separating a module's types into their own file makes sense once multiple files need to import just the shape without pulling in the implementation that produces it, avoiding unnecessary coupling.
Example: Separate Types from Implementation When Useful
// user-types.ts
interface User { id: number; name: string; }
// user-service.ts would import just the User type from user-types.ts
const u: User = { id: 1, name: "Ravi" };
console.log(u);
Avoid Circular Dependencies
Circular dependencies — where module A imports from module B which imports back from A — can cause values to be undefined at import time due to evaluation order, so structuring imports as a one-directional graph avoids a whole class of hard-to-debug bugs.
Example: Avoid Circular Dependencies
// a.ts imports from b.ts, b.ts imports from a.ts -> undefined at import time
// Fix: extract shared code into a third module both can import from.
console.log("Structure imports as a one-directional graph");
Keep Entry Points Small
Keeping an entry point file (like index.ts) limited to re-exports and wiring, rather than actual logic, makes it easy to see a module's full public surface at a glance without reading implementation details.
Example: Keep Entry Points Small
// index.ts
export { formatDate } from "./date-utils";
export { UserService } from "./user-service";
console.log("index.ts only re-exports, no real logic lives here");
Chapter Quiz — Complete all 7 topics to unlock
0/7 topics done
Complete these topics first: