Why I Switched from Express.js to NestJS — And Why I’m Not Going Back
As a Node.js developer, I spent a lot of time with Express.js. It was lightweight, unopinionated, and quick to set up. But as my apps grew, so did the problems: inconsistent structure, tangled business logic, and growing pain when scaling teams or codebases.
Then I tried NestJS.
It completely changed how I write backend code. Here’s why.
Express Works — Until It Doesn’t
Express is great for small projects. But on bigger apps, I ran into the same issues over and over:
- No built-in architecture or separation of concerns.
- Repetitive boilerplate.
- Hard to test without setting up patterns manually.
Here’s an example from a typical Express controller:
// routes/user.js
const express = require('express');
const router = express.Router();
const db = require('../db');
router.get('/users/:id', async (req, res) => {
const user = await db.getUser(req.params.id);
if (!user) {
return res.status(404).send('User not found');
}
res.json(user);
});
module.exports = router;
Looks fine — until your app has 20 routes, 10 middleware functions, and multiple layers of logic mixed in.
NestJS Gave Me Structure and Sanity
1. Clear Architecture by Default
NestJS follows the MVC pattern and organizes code into modules, controllers, and services. This immediately made my code easier to manage and scale.
Here’s the same “get user by ID” example in NestJS:
// user.controller.ts
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get(':id')
async findOne(@Param('id') id: string) {
return this.userService.findById(id);
}
}
// user.service.ts
@Injectable()
export class UserService {
constructor(private readonly prisma: PrismaService) {}
async findById(id: string) {
return this.prisma.user.findUnique({ where: { id } });
}
}
This separation makes testing, debugging, and scaling so much easier.
2. Dependency Injection That Just Works
NestJS comes with DI built in. No need to wire up external containers or build your own patterns. Example:
@Injectable()
export class EmailService {
sendWelcomeEmail(user: User) {
// logic here
}
}
Then inject it wherever it’s needed:
constructor(private readonly emailService: EmailService) {}
In Express, I’d have to pass dependencies manually or use a third-party lib.
3. Typescript First
NestJS is built for TypeScript. Everything — from routes to services to middleware — has strong typing out of the box. Example with DTO validation:
// create-user.dto.ts
export class CreateUserDto {
@IsEmail()
email: string;
@Length(8, 32)
password: string;
}
And then in your controller:
@Post()
create(@Body() dto: CreateUserDto) {
return this.userService.create(dto);
}
Nest automatically validates the input before it reaches your business logic. Clean, safe, and readable.
4. Built-In Features I Used to Build Manually
Nest comes with a huge ecosystem of integrations:
- Swagger with
@nestjs/swagger
- GraphQL with decorators
- Websockets with simple gateways
- Microservices with built-in transport layers
- Authentication via Passport strategies
Adding Swagger, for example, is just:
@ApiTags('users')
@ApiResponse({ status: 200, description: 'User found.' })
@Get(':id')
findOne(@Param('id') id: string) { ... }
Documentation is auto-generated.
5. Team-Friendly Codebase
Once my team started using NestJS, onboarding became way faster. The structure is consistent, so new developers don’t have to guess how things work. They just follow the patterns.
We also started writing more unit tests because DI and modularity made it simple.
NestJS — It’s Just Better Engineering
Yes, the learning curve is real. You’ll need to understand:
- Decorators
- Modules and providers
- The lifecycle of services
But once I got past that, the productivity gains were obvious.
If I need to spin up a script, webhook handler, or quick prototype, I’ll still reach for Express. It’s fast and simple.
NestJS gave me what Express couldn’t:
- Structure
- Scalability
- Built-in patterns
- Cleaner, testable code
Just Once you try it on a real project. And you’ll like it.
That’s all. I hope it was helpful…