Chapter 32 — Express vs NestJS
📖 Definitions
- Express — a minimalist, unopinionated Node.js web framework.
- NestJS — a TypeScript-first, opinionated framework built on top of Express (or optionally Fastify), inspired by Angular's architecture (modules, controllers, providers, decorators).
🔍 Comparison
| Aspect | Express | NestJS |
|---|---|---|
| Style | Minimalist | Opinionated, modular |
| Language | JS/TS (either) | TypeScript-first |
| Architecture | You design it | Modules + DI built-in |
| DI | Manual | Built-in IoC container |
| Validation | Manual (Joi, Zod) | class-validator + DTOs |
| Boilerplate | Low | Higher (but consistent) |
| Testing | Roll-your-own setup | Built-in test utilities |
| Best for | Small services, prototypes, niche apps | Large enterprise apps, teams |
| Performance | Fast, minimal overhead | Slightly heavier (acceptable for most cases) |
💻 Code Example — Same Endpoint in Both
Express
// app.js
import express from "express";
import { z } from "zod";
const app = express();
app.use(express.json());
const CreateUserSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
const users = [];
app.post("/users", (req, res) => {
const parsed = CreateUserSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ errors: parsed.error.flatten() });
}
const user = { id: users.length + 1, ...parsed.data };
users.push(user);
res.status(201).json(user);
});
app.listen(3000);NestJS
// user.dto.ts
import { IsEmail, MinLength } from "class-validator";
export class CreateUserDto {
@IsEmail() email: string;
@MinLength(8) password: string;
}
// user.controller.ts
@Controller("users")
export class UserController {
constructor(private readonly userService: UserService) {}
@Post()
create(@Body() dto: CreateUserDto) {
return this.userService.create(dto);
}
}
// user.service.ts
@Injectable()
export class UserService {
private users: any[] = [];
create(dto: CreateUserDto) {
const user = { id: this.users.length + 1, ...dto };
this.users.push(user);
return user;
}
}
// user.module.ts
@Module({
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
await app.listen(3000);
}
bootstrap();Same endpoint — Nest is more code upfront, but scales much better as the app grows.
💻 Code Example — DI Difference
Express (Manual)
// You manually instantiate and wire dependencies
const db = createDb();
const userRepo = new UserRepository(db);
const userService = new UserService(userRepo);
const userCtrl = new UserController(userService);
app.get("/users/:id", (req, res) => userCtrl.get(req, res));NestJS (Automatic)
@Injectable()
export class UserService {
constructor(private readonly userRepo: UserRepository) {} // ← injected automatically
}
// You declare in module, Nest handles the wiring:
@Module({
providers: [UserService, UserRepository, DbProvider],
})
export class UserModule {}💻 Code Example — Testing
// NestJS — easy to swap dependencies
const module = await Test.createTestingModule({
controllers: [UserController],
providers: [
UserService,
{ provide: UserRepository, useValue: mockRepo }, // ← mock
],
}).compile();
const ctrl = module.get(UserController);
expect(await ctrl.get("1")).toEqual({ id: "1", name: "Mock" });In Express you can do the same, but you build the harness yourself.
📊 When to Choose Which
| Scenario | Pick |
|---|---|
| 1–2 endpoints, quick PoC | Express |
| Lambdas / small workers | Express / native handlers |
| Internal CLI / scripts | Express / none |
| Large team, complex domain | NestJS |
| Need OpenAPI, validation, DI out of the box | NestJS |
| You want strict structure to onboard juniors quickly | NestJS |
🎯 Likely Interview Questions
- Difference between Express and NestJS?
- Why use NestJS over Express? — Built-in DI, modules, validation, testing utilities, OpenAPI, guards/interceptors. Better for large codebases.
- What's NestJS inspired by? — Angular (decorators, modules, providers).
- Can NestJS use Express under the hood? — Yes, by default it uses Express. You can switch to Fastify for performance.
← Node: Rate Limiting | Index | Next: Dependency Injection →