September 27, 2026
NestJS.
Mengenal NestJS 12: module, controller, provider & dependency injection, pipes/guards/interceptors, validasi (class-validator & Standard Schema), TypeORM/Prisma, testing, dan adapter Express vs Fastify.
NestJS adalah framework backend Node.js yang opinionated dan berbasis TypeScript, terinspirasi dari arsitektur Angular: module, decorator, dan dependency injection. Nest tidak menulis HTTP server sendiri β ia berjalan di atas Express (default) atau Fastify lewat platform adapter.
Versi yang dibahas: NestJS 12 (@nestjs/core 12.1.x per September 2026). Poin penting v12:
- Semua paket inti Nest kini ESM-only.
- Runtime butuh Node.js 20.19+ / 22.12+ / 24+; Nest CLI (generate/upgrade) butuh Node 22.22.3+ / 24.15+ / 26+.
- Project baru dengan ESM default memakai Vitest; CommonJS masih memakai Jest.
- Validasi bisa memakai Standard Schema (Zod, Valibot, ArkType) langsung di decorator
@Body().
1. Filosofi
- Arsitektur dulu β Nest memaksa struktur: setiap fitur adalah module berisi controller dan provider. Cocok untuk tim besar dan codebase jangka panjang.
- Dependency Injection β kelas tidak membuat dependency sendiri; container Nest yang menyuntikkannya via constructor.
- Decorator-driven β routing, validasi, guard, dan metadata dideklarasikan dengan decorator.
- Platform-agnostic β logika aplikasi tidak bergantung langsung pada Express/Fastify, dan bisa dipakai juga untuk microservice, GraphQL, WebSocket, dan CLI.
2. Struktur Project
npm i -g @nestjs/cli
nest new nest-api
Hasil generate lalu dikembangkan per fitur:
nest-api/
βββ src/
β βββ main.ts # bootstrap NestFactory
β βββ app.module.ts # root module
β βββ common/
β β βββ guards/auth.guard.ts
β β βββ interceptors/logging.interceptor.ts
β β βββ filters/http-exception.filter.ts
β βββ prisma/
β β βββ prisma.module.ts
β β βββ prisma.service.ts
β βββ users/
β βββ users.module.ts
β βββ users.controller.ts
β βββ users.controller.spec.ts
β βββ users.service.ts
β βββ dto/
β βββ create-user.dto.ts
βββ test/
β βββ app.e2e-spec.ts
βββ nest-cli.json
βββ tsconfig.json
βββ package.json
Generator CLI mempercepat pembuatan file:
nest g resource users # module + controller + service + DTO sekaligus
nest g guard common/guards/auth
3. Module, Provider, dan Dependency Injection
// users.service.ts
import { Injectable, NotFoundException } from '@nestjs/common'
@Injectable()
export class UsersService {
private users = [{ id: 1, name: 'Fanny', email: '[email protected]' }]
findAll() { return this.users }
findOne(id: number) {
const user = this.users.find((u) => u.id === id)
if (!user) throw new NotFoundException('User not found')
return user
}
}
// users.module.ts
import { Module } from '@nestjs/common'
@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService], // agar bisa dipakai module lain
})
export class UsersModule {}
| Konsep | Peran |
|---|---|
@Module() | Unit organisasi: imports, controllers, providers, exports |
@Injectable() | Kelas yang bisa diinjeksi (service, repository, helper) |
| Scope provider | Default singleton; bisa REQUEST atau TRANSIENT (lebih mahal) |
| Custom provider | useValue, useClass, useFactory β misalnya untuk config atau mock |
Dengan DI, unit test tinggal mengganti provider asli dengan mock tanpa mengubah kode aplikasi.
4. Routing (Controller)
import { Controller, Get, Post, Param, Body, ParseIntPipe, HttpCode } from '@nestjs/common'
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
findAll() {
return this.usersService.findAll()
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.usersService.findOne(id)
}
@Post()
@HttpCode(201)
create(@Body() dto: CreateUserDto) {
return this.usersService.create(dto)
}
}
Route dibangun dari decorator lalu didaftarkan ke router milik adapter (Express atau Fastify). Nest 12 menambahkan opsi opt-in routeConflictPolicy untuk mendeteksi route yang saling menutupi.
5. Request Pipeline: Middleware, Guard, Interceptor, Pipe, Filter
Nest memecah “middleware” menjadi beberapa lapisan dengan tanggung jawab jelas. Urutan eksekusinya:
| Urutan | Lapisan | Fungsi | Contoh |
|---|---|---|---|
| 1 | Middleware | Fungsi gaya Express/Fastify sebelum routing Nest | Logging, CORS, cookie |
| 2 | Guard | Boleh lanjut atau tidak (canActivate) | Auth, role |
| 3 | Interceptor (sebelum) | Bungkus eksekusi handler (RxJS) | Timing, cache |
| 4 | Pipe | Transformasi & validasi argumen | ValidationPipe, ParseIntPipe |
| 5 | Handler | Method controller | β |
| 6 | Interceptor (sesudah) | Ubah response | Bungkus { data } |
| 7 | Exception filter | Tangani error | Format error seragam |
// guard sederhana
@Injectable()
export class ApiKeyGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest()
return req.headers['x-api-key'] === process.env.API_KEY
}
}
// pakai per controller/route
@UseGuards(ApiKeyGuard)
@Controller('admin')
export class AdminController {}
6. Validasi
Cara klasik: class-validator + ValidationPipe
npm i class-validator class-transformer
// dto/create-user.dto.ts
import { IsEmail, IsNotEmpty } from 'class-validator'
export class CreateUserDto {
@IsNotEmpty()
name: string
@IsEmail()
email: string
}
// main.ts
app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }))
Cara baru di v12: Standard Schema (Zod, Valibot, ArkType)
Tanpa DTO berbasis class:
import { z } from 'zod'
export const createUserSchema = z.object({
name: z.string().min(1),
email: z.email(),
})
export type CreateUserDto = z.infer<typeof createUserSchema>
// main.ts
import { StandardSchemaValidationPipe } from '@nestjs/common'
app.useGlobalPipes(new StandardSchemaValidationPipe())
// controller
@Post()
create(@Body({ schema: createUserSchema }) dto: CreateUserDto) {
return this.usersService.create(dto)
}
Error validasi keduanya menghasilkan 400 Bad Request.
7. Data Layer / ORM
| Pilihan | Integrasi | Catatan |
|---|---|---|
| TypeORM | @nestjs/typeorm (resmi) | Entity + decorator, @InjectRepository() β terasa paling “Nest” |
| Prisma | Buat PrismaService sendiri | Schema file, client ter-generate, type-safe |
| Drizzle | Provider custom | Ringan, SQL-like |
| MikroORM | @mikro-orm/nestjs | Unit of Work, Data Mapper |
| Mongoose | @nestjs/mongoose (resmi) | MongoDB |
Contoh TypeORM:
@Entity()
export class User {
@PrimaryGeneratedColumn() id: number
@Column() name: string
@Column({ unique: true }) email: string
}
@Injectable()
export class UsersService {
constructor(@InjectRepository(User) private repo: Repository<User>) {}
findAll() { return this.repo.find() }
}
Pola Prisma yang umum: PrismaService sebagai provider yang membuka koneksi di hook onModuleInit, lalu diekspor oleh PrismaModule. Detail inisialisasi client Prisma berubah antar versi, jadi ikuti resep di docs.nestjs.com/recipes/prisma
.
8. Testing
Nest menyediakan @nestjs/testing untuk membangun module uji dengan provider yang bisa di-override.
// users.controller.spec.ts (Vitest)
import { Test } from '@nestjs/testing'
import { describe, it, expect, beforeEach } from 'vitest'
describe('UsersController', () => {
let controller: UsersController
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
controllers: [UsersController],
providers: [
{ provide: UsersService, useValue: { findAll: () => [{ id: 1, name: 'Mock' }] } },
],
}).compile()
controller = moduleRef.get(UsersController)
})
it('mengembalikan daftar user', () => {
expect(controller.findAll()).toHaveLength(1)
})
})
Untuk e2e, buat app dari module uji (moduleRef.createNestApplication()) lalu tembak dengan Supertest. Jika memakai adapter Fastify, bisa juga memakai app.inject().
9. Performa
NestJS menambah lapisan abstraksi di atas HTTP server: resolusi DI, eksekusi guard/interceptor/pipe, dan pembacaan metadata decorator. Karena itu throughput Nest tidak bisa melebihi adapter di bawahnya, dan biasanya sedikit di bawahnya.
Hal-hal yang memengaruhi performa:
- Pilihan adapter β Nest di atas Fastify mewarisi router find-my-way dan overhead Fastify yang rendah; di atas Express mewarisi router linear Express.
- Provider scope β
REQUEST-scoped provider membuat instance baru per request dan menular ke provider yang bergantung padanya. Gunakan singleton kecuali benar-benar perlu. class-transformerpadaValidationPipe({ transform: true })menambah kerja per request.- Interceptor RxJS yang berlapis-lapis.
Beralih ke Fastify cukup mengganti factory:
npm i @nestjs/platform-fastify
import { NestFactory } from '@nestjs/core'
import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify'
const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter())
await app.listen(process.env.PORT ?? 3000, '0.0.0.0')
Catatan: middleware khusus Express (mis. helmet versi Express) perlu diganti padanannya (@fastify/helmet).
Benchmark resmi fastify/benchmarks
(run 2 September 2026) tidak memuat NestJS, jadi aku tidak mencantumkan angka req/s untuk Nest. Sebagai patokan saja: di run yang sama Fastify 5.12.1 β 97.595 req/s dan Express 5.2.1 β 59.651 req/s β Nest akan berada sedikit di bawah adapter yang dipakainya. Ukur sendiri dengan autocannon jika angka ini penting.
10. Ekosistem & Kematangan
| Aspek | Penilaian |
|---|---|
| Umur | Sejak 2017, rilis mayor rutin |
| Paket resmi | config, typeorm, mongoose, graphql, swagger, microservices, websockets, schedule, bull/bullmq, terminus, cqrs |
| Observability | v12 memperkenalkan @nestjs/observe |
| TypeScript | First-class |
| Dokumentasi | docs.nestjs.com β lengkap, banyak resep |
| Kurva belajar | Paling tinggi di antara framework di shelf ini (DI, decorator, RxJS) |
11. Contoh Minimal yang Bisa Dijalankan
Cara paling cepat tetap lewat CLI (nest new), tapi berikut contoh satu file supaya konsepnya terlihat utuh:
mkdir nest-demo && cd nest-demo
npm init -y
npm install @nestjs/core @nestjs/common @nestjs/platform-express reflect-metadata rxjs
npm install -D typescript @types/node
tsconfig.json (decorator wajib aktif):
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"types": ["node"],
"skipLibCheck": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"strict": false
}
}
main.ts:
import 'reflect-metadata'
import { Controller, Get, Injectable, Module, Param, ParseIntPipe } from '@nestjs/common'
import { NestFactory } from '@nestjs/core'
@Injectable()
class UsersService {
private users = [{ id: 1, name: 'Fanny' }, { id: 2, name: 'Budi' }]
findAll() { return this.users }
findOne(id: number) { return this.users.find((u) => u.id === id) ?? null }
}
@Controller('users')
class UsersController {
constructor(private readonly users: UsersService) {}
@Get()
findAll() { return this.users.findAll() }
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) { return this.users.findOne(id) }
}
@Module({ controllers: [UsersController], providers: [UsersService] })
class AppModule {}
const app = await NestFactory.create(AppModule)
await app.listen(3000)
console.log('http://localhost:3000/users')
Jalankan dengan tsc lalu node, karena DI Nest bergantung pada emitDecoratorMetadata (tidak semua transpiler cepat mendukungnya):
npm pkg set type=module
npx tsc && node main.js
curl localhost:3000/users/1
Untuk project sungguhan, gunakan nest new yang sudah menyiapkan build (SWC/tsc), lint, dan test runner.
12. Kapan Memilih NestJS?
- Tim besar yang butuh konsistensi struktur dan konvensi.
- Aplikasi enterprise dengan banyak domain, microservice, GraphQL, queue.
- Tim yang terbiasa dengan Angular, Spring, atau .NET (pola DI + decorator).
Kalau ingin struktur full-stack yang juga “batteries included” tapi lebih mirip Laravel, lihat AdonisJS . Ringkasan pilihan ada di Perbandingan Framework Node.js .
Referensi

Hey! I’m Fanny, the software engineer tending to this digital garden. You can read more about me, or subscribe by email.