Why the default flat src/ falls apart once a project has real features, and the module-driven layout that replaces it — one directory per feature, with common, config and database kept alongside.

When you first create a NestJS project, you’re greeted with a simple and clean folder structure. You have app.controller.ts, app.service.ts, and app.module.ts. For a "Hello World" project, this is more than enough.
But what happens when your project grows? What if you have dozens of endpoints, complex business logic, and various interconnected features? The default structure quickly becomes crowded and messy.
This is where thinking about a scalable folder architecture from the beginning is crucial. This article will guide you through a best-practice approach that keeps your project clean and maintainable: the Module-Driven Structure.
As you add features like Users, Products, Orders, and Auth, a flat structure in your src folder becomes chaotic:
src/
├── users.controller.ts
├── products.controller.ts
├── orders.controller.ts
├── auth.controller.ts
├── users.service.ts
├── products.service.ts
├── orders.service.ts
└── ... and so onFinding files related to a single feature becomes a chore because its components are scattered. This is inefficient and a nightmare to maintain.
The core idea is to group all files related to a single feature into their own module. But we can take it one step further for ultimate clarity by creating a dedicated modules directory.
This approach creates a clean separation between your application’s core business logic and its supporting code (like configuration and common helpers).
Here is the highly recommended structure:
src/
├── modules/ # All business logic and features live here
│ ├── auth/ # Everything about authentication & authorization
│ │ ├── strategies/
│ │ ├── guards/
│ │ ├── dto/
│ │ ├── auth.controller.ts
│ │ ├── auth.module.ts
│ │ └── auth.service.ts
│ │
│ ├── products/ # "Products" feature module
│ │ ├── dto/
│ │ ├── entities/
│ │ ├── products.controller.ts
│ │ ├── products.module.ts
│ │ └── products.service.ts
│ │
│ └── users/ # "Users" feature module
│ ├── dto/
│ ├── entities/
│ ├── users.controller.ts
│ ├── users.module.ts
│ └── users.service.ts
│
├── common/ # Reusable components (pipes, filters, etc.)
│
├── config/ # Configuration management
│
├── database/ # Migrations, seeds, etc.
│
├── app.module.ts # Root module, where all feature modules are imported
└── main.ts # Application entry pointLet’s look at the role of each top-level directory:
This is the heart of your application. Each sub-directory represents a self-contained feature or business domain.
This folder is your project’s “toolbox.” All reusable components, functions, or classes that can be used across multiple features are placed here. Examples include a custom decorator to get user data from a request, or an exception filter to handle errors consistently across the entire application.
Never hardcode database credentials or API keys. NestJS has an excellent @nestjs/config module for managing environment variables. This folder is the place to house all that configuration logic.
If your project uses database migrations or seeding, this folder is the perfect place to keep them organized and separate from your application logic.
Adopting a robust folder structure from the start is a long-term investment in your project’s health. By grouping your features inside a dedicated modules directory, you create a clean, scalable, and highly maintainable architecture that will serve you well as your application grows.
Start organizing your code this way, and your future self will thank you for it! Happy coding!