Web Backend Development on Node.js (NestJS)
NestJS—not just framework, architectural solution. Enforces structure team would otherwise invent: modules, providers, dependency injection, decorators. For complex projects with multiple developers this is value. For small API alone—overkill.
NestJS justified when: monorepo with multiple apps, microservices architecture, team of 3+ developers, long-term project with changing team.
Module Structure
Each functional block—separate module. Module declares what it exports and imports:
// users/users.module.ts
@Module({
imports: [TypeOrmModule.forFeature([User]), JwtModule],
controllers: [UsersController],
providers: [UsersService, UsersRepository],
exports: [UsersService]
})
export class UsersModule {}
// app.module.ts
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
TypeOrmModule.forRootAsync({
useFactory: (config: ConfigService) => ({
type: 'postgres',
url: config.get('DATABASE_URL'),
entities: [__dirname + '/**/*.entity{.ts,.js}']
}),
inject: [ConfigService]
}),
UsersModule,
ProductsModule,
AuthModule,
OrdersModule,
]
})
export class AppModule {}
Controllers and Validation
Controller handles HTTP layer only: routes, headers, status codes. Logic—in services.
@Controller('users')
@UseGuards(JwtAuthGuard)
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
@Roles('admin')
findAll(@Query() paginationDto: PaginationDto): Promise<PaginatedResult<User>> {
return this.usersService.findAll(paginationDto)
}
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number): Promise<User> {
const user = await this.usersService.findById(id)
if (!user) throw new NotFoundException(`User ${id} not found`)
return user
}
@Post()
@HttpCode(HttpStatus.CREATED)
create(@Body() createUserDto: CreateUserDto): Promise<User> {
return this.usersService.create(createUserDto)
}
}
Built-in pipes validate and transform DTO automatically. Decorators @Roles, @UseGuards apply authorization before controller.
Services and Dependency Injection
// users.service.ts
@Injectable()
export class UsersService {
constructor(
private readonly usersRepository: UsersRepository,
private readonly jwtService: JwtService,
private readonly config: ConfigService
) {}
async create(createUserDto: CreateUserDto): Promise<User> {
const existing = await this.usersRepository.findByEmail(createUserDto.email)
if (existing) throw new ConflictException('Email already exists')
const user = await this.usersRepository.create({
...createUserDto,
password: await this.hashPassword(createUserDto.password)
})
return user
}
private async hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, 10)
}
}
Services injectable—no manual instantiation. Container manages lifetime (singleton, request-scoped, transient).
Database with TypeORM
// user.entity.ts
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number
@Column({ unique: true })
email: string
@Column()
passwordHash: string
@Column({ type: 'enum', enum: UserRole, default: UserRole.USER })
role: UserRole
@CreateDateColumn()
createdAt: Date
}
OpenAPI Documentation
NestJS generates Swagger docs automatically:
npm install @nestjs/swagger swagger-ui-express
// main.ts
const config = new DocumentBuilder()
.setTitle('My API')
.setVersion('1.0')
.addBearerAuth()
.build()
const document = SwaggerModule.createDocument(app, config)
SwaggerModule.setup('api/docs', app, document)
Testing
NestJS built-in testing module:
// users.service.spec.ts
describe('UsersService', () => {
let service: UsersService
let repo: UsersRepository
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{ provide: UsersRepository, useValue: mockRepository }
]
}).compile()
service = module.get<UsersService>(UsersService)
repo = module.get<UsersRepository>(UsersRepository)
})
it('should create user', async () => {
const result = await service.create({ email: '[email protected]', password: '123' })
expect(result).toBeDefined()
expect(repo.create).toHaveBeenCalled()
})
})
Timeline
Small REST API with NestJS—1–2 weeks: modules, auth, basic CRUD. Medium monorepo (3 services)—4–6 weeks. Complex system—depends on features, typically 8+ weeks.
NestJS investment pays off in team of 3+ and long-term projects—boilerplate amortizes.







