How a NestJS App Hatches — Explained Like You're a Sleep-Deprived Dev
So you've chosen NestJS — the TypeScript back-end framework that looks like Angular and feels like Express got a spa day. But how does it actually work under the hood?
Let’s walk through the life of a request in NestJS — from birth (main.ts
) to glory (Controller/Service
) — with a healthy dash of humor.
🎬 Scene 1: The App is Born — main.ts
This is where everything begins. This file is like Iron Man shouting “I AM… the app entry point!”
// src/main.ts
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix("api"); // So all routes start with /api
await app.listen(3000);
}
bootstrap();
💡 TL;DR
main.ts
boots your app.- Want all APIs to start with
/api
? Do it here. - Want to add global guards, pipes, or CORS? Yup — here.
🧱 Scene 2: The Blueprint — AppModule
The AppModule
is the real MVP. Think of it as the city planner — it decides what modules get in the city and which ones stay out.
// src/app.module.ts
@Module({
imports: [UsersModule, AuthModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
🔥 Quick Recap
- You define feature modules here.
- Import anything you need globally.
- If
main.ts
is Iron Man,AppModule
is the Avengers HQ.
🛣️ Scene 3: Roads & Routes — Controller
Now it’s time for roads (aka routes) to be built. Say hello to @Controller
.
@Controller("users") // Base route: /users
export class UsersController {
@Get() // GET /users
findAll() {
return this.userService.findAll();
}
@Get(":id") // GET /users/123
findOne(@Param("id") id: string) {
return this.userService.findOne(id);
}
}
🛑 Pro Tip: You can use decorators like @Post()
, @Put()
, @Delete()
just like toppings on your pizza.
🧙♂️ Scene 4: The Real Brains — Service
Services are the Gandalf of NestJS. They do the real work: talking to databases, processing data, or telling other services what to do.
@Injectable()
export class UsersService {
findAll() {
return [{ name: "Kahnu" }];
}
findOne(id: string) {
return { id, name: "Kahnu" };
}
}
🛡️ Scene 5: Guards, Interceptors & Pipes — The Bouncers 🕴️
When you hit a controller, there might be guards at the door checking ID (auth), interceptors fixing your hair (modifying responses), and pipes ensuring you're clean (validation).
@UseGuards(JwtAuthGuard)
@Get('profile')
getProfile(@Request() req) {
return req.user;
}
🧵 Scene 6: A Request's Life Journey
Let’s follow a request like it’s Simba running through the Pride Lands:
Client ➝ main.ts ➝ AppModule ➝ Controller ➝ Guard ➝ Pipe ➝ Interceptor ➝ Service ➝ Response
+-------------+
| main.ts | 🧠 Boots the app
+-------------+
↓
+-------------+
| AppModule | 🧩 Imports modules
+-------------+
↓
+-------------+
| Controller | 🚦 Handles routes
+-------------+
↓
+-------------+
| Guard | 🛡️ Checks auth
+-------------+
↓
+-------------+
| Pipe | 🚿 Validates/cleans input
+-------------+
↓
+-------------+
| Interceptor | 💄 Enhances response
+-------------+
↓
+-------------+
| Service | 🔧 Does business logic
+-------------+
↓
🎉 Response
🔧 Where to Put Bootstrap-Level Config
In main.ts
:
app.setGlobalPrefix("api"); // Prefixes all routes with /api
app.enableCors(); // CORS for frontend love
app.useGlobalPipes(new ValidationPipe()); // DTO validation
🤔 Do I Always Need a Service?
Nope. For a simple “Hello World” endpoint, a controller alone works:
@Controller()
export class AppController {
@Get()
hello() {
return "Hello from NestJS!";
}
}
But for anything more than that? Use a service — it keeps things clean and testable.
🔥 Final Pro Tips
- Use DTOs (
*.dto.ts
) to validate incoming data. - Use Guards (
JwtAuthGuard
,RolesGuard
) to control access. - Use Modules to break up features like LEGO blocks.
- Your app starts in
main.ts
, lives inmodules
, and shines throughcontrollers
.