TL;DR: NestJS is a progressive Node.js framework for building server-side applications. Built on top of Express (or Fastify), it provides an opinionated structure…
The analogy
Express is like a studio apartment — everything in one room, you decide where stuff goes. NestJS is like a well-designed house with labeled rooms: kitchen (services), front door (controllers), hallways (modules). Both work, but the house is WAY easier to manage when you have 50 rooms!
What is it, really?
NestJS is a progressive Node.js framework for building server-side applications. Built on top of Express (or Fastify), it provides an opinionated structure inspired by Angular, using modules, controllers, and services. It includes powerful features like Pipes, Interceptors, Filters, Middleware, and custom Decorators.
Why it matters in the real world
NestJS is used by Adidas, Roche, Autodesk, and many startups. It's the most popular structured Node.js framework, with over 70K GitHub stars.
The 11 things you actually need to know
Built-in Structure — Module → Controller → Service pattern. Everyone organizes code the same way — no more 'where did John put that file?'
Dependency Injection — Instead of manually creating instances, you just declare what you need and NestJS delivers it. Like room service!
TypeScript First — NestJS is built FOR TypeScript. Decorators (@get, @post, @Injectable) make code readable and self-documenting.
Batteries Included — Validation, auth guards, Swagger docs, WebSockets, microservices — all built-in or plug-and-play.
Scalable from Day 1 — Start small with one controller. Grow to 100 modules without refactoring. NestJS scales with your team!
Rich Ecosystem — @nestjs/jwt, @nestjs/passport, @nestjs/websockets, @nestjs/graphql. Add what you need, nothing more.
Pipes — Data Transformation & Validation — Pipes transform or validate incoming data before it reaches your controller. ValidationPipe automatically checks DTOs, ParseIntPipe converts strings to numbers. Think of pipes as airport security — they inspect your luggage (data) before you board the plane (controller).
Interceptors — Before & After Logic — Interceptors wrap around your route handler. They can transform the response, add logging, cache results, or measure execution time. Like a sandwich — interceptor code runs before AND after your handler. Example: automatically wrapping all responses in {success: true, data: ...} format.
Exception Filters — Centralized Error Handling — Instead of try/catch everywhere, exception filters catch errors globally and return consistent error responses. Create a custom filter to log errors, send alerts, and return user-friendly messages. One place to handle ALL errors.
Middleware — Request Pipeline — Middleware runs BEFORE the route handler on every request. Use it for logging, CORS, rate limiting, or authentication checks. It's the bouncer at the door — checking everyone before they enter the club (your controller).
Custom Decorators — Create your own decorators like @CurrentUser() to extract the logged-in user from the request, or @Roles('admin') to mark routes. Custom decorators make your code cleaner and more readable — hiding complexity behind a simple @ symbol.
Working code example
// The NestJS Holy Trinity 🔺
// 1. MODULE — wires everything together
@Module({
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
// 2. CONTROLLER — handles HTTP requests (the front door 🚪)
@Controller('users')
export class UserController {
constructor(private userService: UserService) {}
// ↑ NestJS auto-injects this! Magic! ✨
@Get() // GET /users
findAll() {
return this.userService.findAll();
}
@Get(':id') // GET /users/123
findOne(@Param('id') id: string) {
return this.userService.findOne(id);
}
@Post() // POST /users
create(@Body() data: CreateUserDto) {
return this.userService.create(data);
}
}
// 3. SERVICE — business logic (the brain 🧠)
@Injectable()
export class UserService {
constructor(private prisma: PrismaService) {}
findAll() {
return this.prisma.user.findMany();
}
findOne(id: string) {
return this.prisma.user.findUnique({ where: { id } });
}
create(data: CreateUserDto) {
return this.prisma.user.create({ data });
}
}
// DTO — validate input 📋
import { IsString, IsEmail, MinLength } from 'class-validator';
export class CreateUserDto {
@IsString()
@MinLength(2)
name: string;
@IsEmail()
email: string;
}
Line-by-line breakdown:
- The NestJS Holy Trinity 🔺
- 1. MODULE — wires everything together
- Decorator that adds metadata or behavior
- Exporting for use in other files
- 2. CONTROLLER — handles HTTP requests (the front door 🚪)
- Decorator that adds metadata or behavior
- Exporting for use in other files
- ↑ NestJS auto-injects this! Magic! ✨
- Decorator that adds metadata or behavior
- Returning a value
- Closing block
- Decorator that adds metadata or behavior
- Returning a value
- Closing block
- Decorator that adds metadata or behavior
- Returning a value
- Closing block
- Closing block
- 3. SERVICE — business logic (the brain 🧠)
- Decorator that adds metadata or behavior
- Exporting for use in other files
- Returning a value
- Closing block
- Returning a value
- Closing block
- Returning a value
- Closing block
- Closing block
- DTO — validate input 📋
- Importing required dependencies
- Exporting for use in other files
- Decorator that adds metadata or behavior
- Decorator that adds metadata or behavior
- Decorator that adds metadata or behavior
- Closing block
Spot the bug
@Controller('users')
export class UserController {
constructor(private userService: UserService) {}
@Get()
findAll() {
return this.userService.findAll();
}
}
export class UserService {
findAll() { return []; }
}
Hint
What decorator is missing from the service class?
Answer
UserService is missing the @Injectable() decorator. Without it, NestJS can't inject it via dependency injection. Fix: add @Injectable() above 'export class UserService'.
Explain it like I'm five
Express is like a messy room where you put stuff wherever. NestJS is like a room with labeled shelves - toys here, books there, clothes in the closet. Both rooms work, but the organized one is way easier when you have LOTS of stuff!
Fun fact
NestJS was created by Kamil Myśliwiec in 2017 because he was frustrated that Node.js had no structure like Angular did for frontend. He basically thought 'why can't backend be organized too?' and just... built it. 😤🛠️
Try it yourself
Create a NestJS project with nest new my-app, then generate a module with nest g resource cats. Look at the files it creates — controller, service, module, DTO. Notice how they connect!
Want the interactive version?
This article is lesson 15 of Free Full-Stack Playbook — a free, interactive course with quizzes, a code playground, spaced-repetition flashcards, and 48 more lessons. No signup required.
- 📖 Open this lesson with quizzes + code playground
- 🎓 See the full 49-lesson curriculum
- 🏠 All 16 free courses on Teamz Lab Learning
Related lessons from this course
- JavaScript vs TypeScript — Adding Superpowers to JS
- Developer Tooling — Work Smarter, Not Harder
- Variables, Types & Arrays — The Building Blocks of Data
Built by Teamz Lab — we build production mobile and web apps for clients worldwide. This free course is how we give back. Free Full-Stack Playbook's interactive version is here.
Originally published at https://tool.teamzlab.com?utm_source=devto&utm_medium=article&utm_campaign=2026-04-why-nestjs-js-ts-fun-book
Top comments (0)