Why NestJS?
Pros
- Clear structure (Module/Controller/Service)
- Dependency Injection (DI) support
- Popular in the job market
- Well-suited for large teams
Cons
- Steep learning curve
- Lots of boilerplate code
- Overkill for small projects
- Must be familiar with decorator syntax
Project Structure
NestJS uses a module-based architecture. Each feature is separated into modules, and modules consist of controllers and services.
Project Structure
src/
├── main.ts # Entry point
├── app.module.ts # Root module
├── health/
│ ├── health.module.ts # Health module
│ └── health.controller.ts # Health controller
└── projects/
├── projects.module.ts # Projects module
├── projects.controller.ts # Handles routing
├── projects.service.ts # Business logic
└── dto/
├── create-project.dto.ts
└── update-project.dto.tsCore Concepts
- Module: A unit that groups related features. Manages dependencies via imports/exports
- Controller: Handles HTTP requests. Responsible for routing and responses
- Service: Business logic. Injected into controllers
- DTO: Data Transfer Object. Used for input validation
Code Examples
1. Main Entry Point
src/main.ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// CORS configuration
app.enableCors({
origin: ['http://localhost:5173'],
credentials: true,
});
// Global validation pipe
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
transform: true,
}));
await app.listen(8000);
console.log('🚀 NestJS running on http://localhost:8000');
}
bootstrap();2. Root Module
src/app.module.ts
import { Module } from '@nestjs/common';
import { HealthModule } from './health/health.module';
import { ProjectsModule } from './projects/projects.module';
@Module({
imports: [HealthModule, ProjectsModule],
})
export class AppModule {}3. DTO (Input Validation)
src/projects/dto/create-project.dto.ts
import { IsString, IsOptional, MinLength, MaxLength } from 'class-validator';
export class CreateProjectDto {
@IsString()
@MinLength(1)
@MaxLength(100)
name: string;
@IsOptional()
@IsString()
@MaxLength(500)
description?: string;
}4. Controller
src/projects/projects.controller.ts
import { Controller, Get, Post, Body, Param, Delete, HttpCode } from '@nestjs/common';
import { ProjectsService } from './projects.service';
import { CreateProjectDto } from './dto/create-project.dto';
@Controller('api/v1/projects') // Route prefix
export class ProjectsController {
// Dependency injection
constructor(private readonly projectsService: ProjectsService) {}
@Post()
@HttpCode(201)
create(@Body() createProjectDto: CreateProjectDto) {
return this.projectsService.create(createProjectDto);
}
@Get()
findAll() {
return this.projectsService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.projectsService.findOne(id);
}
@Delete(':id')
@HttpCode(204)
remove(@Param('id') id: string) {
return this.projectsService.remove(id);
}
}5. Service
src/projects/projects.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateProjectDto } from './dto/create-project.dto';
@Injectable() // Register in DI container
export class ProjectsService {
private projects = new Map();
create(dto: CreateProjectDto) {
const project = {
id: crypto.randomUUID(),
name: dto.name,
description: dto.description || null,
created_at: new Date().toISOString(),
};
this.projects.set(project.id, project);
return project;
}
findAll() {
return {
items: Array.from(this.projects.values()),
total: this.projects.size,
};
}
findOne(id: string) {
const project = this.projects.get(id);
if (!project) throw new NotFoundException('Project not found');
return project;
}
remove(id: string) {
if (!this.projects.delete(id)) {
throw new NotFoundException('Project not found');
}
}
}Hono vs NestJS Comparison
| Category | Hono (Lv.1) | NestJS (Lv.4) |
|---|---|---|
| File Count | 5 | 12 |
| Lines of Code | ~150 lines | ~300 lines |
| Learning Curve | Low | High |
| Structure | Flexible | Opinionated |
| Team Size | 1-3 people | 5+ people |
| Best For | Quick prototypes, simple APIs | Complex domains, long-term maintenance |
Full Example Code
Check out the NestJS version of the B2B Admin API: examples/b2b-admin/api-nest
Next Steps
Once backend deployment is complete, learn how to connect PostgreSQL in Cycle 4: Database.