#3 — Modules, Controllers & Services (Concepts + Examples)
As a former 3D Animator with more than 12 years of experience, I have always been fascinated by the intersection of technology and creativity. That's why I recently shifted my career towards MERN stack development and software engineering, where I have been serving since 2021.
With my background in 3D animation, I bring a unique perspective to software development, combining creativity and technical expertise to build innovative and visually engaging applications. I have a passion for learning and staying up-to-date with the latest technologies and best practices, and I enjoy collaborating with cross-functional teams to solve complex problems and create seamless user experiences.
In my current role as a MERN stack developer, I have been responsible for developing and implementing web applications using MongoDB, Express, React, and Node.js. I have also gained experience in Agile development methodologies, version control with Git, and cloud-based deployment using platforms like Heroku and AWS.
I am committed to delivering high-quality work that meets the needs of both clients and end-users, and I am always seeking new challenges and opportunities to grow both personally and professionally.
1) What is a Module?
A module is a cohesive package of functionality. In NestJS, every feature lives inside a module.
Think: UsersModule for user-related concerns, PostsModule for blog posts, etc.
Each module has a single entry file:
*.module.ts(e.g.,users.module.ts).Modules group their own controllers, services, entities/schemas, and tests.
Why modules?
✅ Clear boundaries per feature
✅ Easier scaling in teams
✅ Reuse via imports/exports
✅ Testability (mock isolated providers)

2) The Root of it all: main.ts → AppModule → your feature modules
(main.ts) → (AppModule) → (UsersModule, PostsModule, ...)
main.tsboots the Nest app.AppModuleis the root module that imports your feature modules.Feature modules can import each other when needed.

Minimal bootstrap (src/main.ts)
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
Root module (src/app.module.ts)
import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';
@Module({
imports: [UsersModule], // add feature modules here
})
export class AppModule {}
3) Module Anatomy
A module is defined with the @Module() decorator and four key slots:
@Module({
imports: [], // other modules this module depends on
controllers: [], // route handlers (HTTP layer)
providers: [], // services, repositories, guards, interceptors, pipes
exports: [], // subset of providers made available to other modules
})
export class FeatureModule {}
Controllers handle routing and I/O.
Services hold business logic and data access.
Providers are injectable classes (DI) — services, guards, interceptors, pipes, factories.
4) Example: Users feature module

users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService], // export if other modules need it
})
export class UsersModule {}
users.controller.ts
import { Controller, Get, Param } from '@nestjs/common';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get(':id')
findOne(@Param('id') id: string) {
return this.usersService.findOne(id);
}
}
users.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class UsersService {
private users = [
{ id: '1', name: 'Ada' },
{ id: '2', name: 'Grace' },
];
findOne(id: string) {
return this.users.find(u => u.id === id);
}
}
Tests (e.g.,
users.controller.spec.ts) live alongside to validate controller behavior; E2E tests go in/test.
5) Wiring Multiple Modules Together
Suppose PostsModule needs UsersService (e.g., to verify a post’s author).
posts.module.ts
import { Module } from '@nestjs/common';
import { PostsService } from './posts.service';
import { PostsController } from './posts.controller';
import { UsersModule } from '../users/users.module';
@Module({
imports: [UsersModule], // we depend on UsersService exported by UsersModule
controllers: [PostsController],
providers: [PostsService],
})
export class PostsModule {}
By putting UsersService in exports of UsersModule, any importing module (like PostsModule) can inject it.

Diagram
main.ts
└─ AppModule
├─ UsersModule (exports: UsersService)
└─ PostsModule (imports: UsersModule) → can inject UsersService
6) Controller vs Service (mental model)
Controller: “When an HTTP request hits
/users/:id, what do we do?” → parse params, delegate to service.Service: “How do we fetch/process data?” → business rules, DB calls, integrations.
Keeping controllers thin and services rich makes your app testable and maintainable.
7) Entities/Schemas inside a Module
Depending on your persistence layer:
TypeORM →
user.entity.tsMongoose →
user.schema.ts
These files describe how data is stored (think: Model in MVC). They are typically provided via the ORM module (e.g., TypeOrmModule.forFeature([...]) or MongooseModule.forFeature([...])) inside your feature module’s imports.
8) Generating Modules & Files via CLI
# module only
nest g module users
# controller inside users
nest g controller users
# service inside users
nest g service users
# full CRUD resource bundle (module + controller + service + DTOs)
nest g resource posts
# simulate before actually writing files
nest g controller users --dry-run
9) Best Practices & Pitfalls
Single-responsibility feature modules (Users, Posts, Auth, Mail, Files…) keep boundaries clean.
Export only what’s needed. Keep provider surfaces small.
Avoid circular imports. If unavoidable, use
forwardRef(() => OtherModule).Shared utilities: create a
SharedModulefor common pipes/guards and export them.Provider scope: defaults to singleton per app/module context — good for stateless services.
Don’t re-import the same module in many places if you can expose required providers via exports from one place.
Testing: mock services in controller tests; do E2E for integration behaviour.
10) Quick Quiz (2 min)
What are the four arrays in
@Module()and what do they do?Where do you place HTTP route handlers? Where should business logic live?
How does one module use a provider from another module?
What’s a simple way to preview what the CLI will generate without touching the disk?
(Answers: controllers/providers/imports/exports; controllers vs services; export provider in source module and import that module; use --dry-run.)
11) Practice Tasks
- Create
usersandpostsmodules.
nest g module users
nest g module posts
- Add a controller + service to
usersand return a mock user by id.
nest g controller users
nest g service users
Export
UsersServicefromUsersModuleand importUsersModuleintoPostsModule.Add a
POST /postsendpoint that validates anauthorIdby callingUsersService.findOne.(Optional) Add unit tests for
UsersControllerwith a mockedUsersService.