One Folder, One Concern
Structuring Feature Modules
Group a feature's controllers, services, DTOs, and entities under a single module. The Nest CLI's resource generator scaffolds the convention for you.
What you'll learn
- Use the resource generator for a full feature scaffold
- Follow the standard feature-module layout
- Re-export shared providers through the module's exports array
A Nest app is a tree of feature modules — UsersModule, OrdersModule,
BillingModule — each owning its slice of the domain. Get the layout right
and your codebase scales without becoming a maze.
The Resource Generator
The CLI scaffolds an entire feature in one command:
nest g resource users You’ll be asked which transport (REST is the default) and whether to generate CRUD entry points. Pick yes and you get:
src/users/
users.module.ts
users.controller.ts
users.service.ts
dto/
create-user.dto.ts
update-user.dto.ts
entities/
user.entity.ts Everything is pre-wired into users.module.ts. Add your business logic and
move on.
The Standard Layout
After a year of Nest, every feature module starts to look the same. Lean into the convention:
// users.module.ts
@Module({
imports: [DatabaseModule], // what this feature needs
controllers: [UsersController], // HTTP surface
providers: [UsersService, UsersRepository],
exports: [UsersService], // what other features can use
})
export class UsersModule {} Four arrays, four jobs: incoming dependencies, HTTP routes, internal providers, outgoing API. Keep them in that order in every file and readability becomes free.
Exports — Your Public API
Anything not in exports is private to the module. Other features that
import UsersModule can only see what’s exported.
// orders.module.ts
@Module({
imports: [UsersModule], // pulls in whatever UsersModule exports
providers: [OrdersService],
})
export class OrdersModule {}
// orders.service.ts can now inject UsersService
@Injectable()
export class OrdersService {
constructor(private readonly users: UsersService) {}
} This is real encapsulation. UsersRepository is registered but not exported,
so no other feature can reach past UsersService into the data layer. Refactor
the repo whenever you like.
A Few Layout Rules That Help
- One feature, one folder. Co-locate the controller, service, DTOs, entities, and tests.
- No cross-feature reach-ins. If
BillingServicewantsUsersService, it goes throughUsersModule’s exports. Never import a file from another feature’s internals. - Shared utilities live in a
shared/orcommon/module. Cross-cutting helpers (pipes, guards, decorators) sit there. AppModulejust composes. It imports feature modules and global modules; it shouldn’t own controllers or services of its own.
Stick to this and a new teammate can navigate the codebase on day one.
Middleware →