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 {}
KonsepPeran
@Module()Unit organisasi: imports, controllers, providers, exports
@Injectable()Kelas yang bisa diinjeksi (service, repository, helper)
Scope providerDefault singleton; bisa REQUEST atau TRANSIENT (lebih mahal)
Custom provideruseValue, 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:

UrutanLapisanFungsiContoh
1MiddlewareFungsi gaya Express/Fastify sebelum routing NestLogging, CORS, cookie
2GuardBoleh lanjut atau tidak (canActivate)Auth, role
3Interceptor (sebelum)Bungkus eksekusi handler (RxJS)Timing, cache
4PipeTransformasi & validasi argumenValidationPipe, ParseIntPipe
5HandlerMethod controllerβ€”
6Interceptor (sesudah)Ubah responseBungkus { data }
7Exception filterTangani errorFormat 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

PilihanIntegrasiCatatan
TypeORM@nestjs/typeorm (resmi)Entity + decorator, @InjectRepository() β€” terasa paling “Nest”
PrismaBuat PrismaService sendiriSchema file, client ter-generate, type-safe
DrizzleProvider customRingan, SQL-like
MikroORM@mikro-orm/nestjsUnit 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-transformer pada ValidationPipe({ 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

AspekPenilaian
UmurSejak 2017, rilis mayor rutin
Paket resmiconfig, typeorm, mongoose, graphql, swagger, microservices, websockets, schedule, bull/bullmq, terminus, cqrs
Observabilityv12 memperkenalkan @nestjs/observe
TypeScriptFirst-class
Dokumentasidocs.nestjs.com β€” lengkap, banyak resep
Kurva belajarPaling 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.

Comments