Pelajari NestJS dari nol dengan membangun REST API yang siap produksi. Panduan praktis ini mencakup setup, konsep inti, dan best practices untuk pengembangan backend modern.

NestJS telah menjadi framework pilihan untuk membangun aplikasi backend yang scalable di ekosistem Node.js. Jika Anda datang dari Express atau Fastify, Anda mungkin bertanya-tanya apa yang membuat NestJS berbeda. Jawabannya terletak pada arsitektur yang opinionated, dependency injection bawaan, dan pendekatan TypeScript-first yang membuat codebase Anda maintainable dan testable sejak hari pertama.
Dalam panduan ini, kami akan membangun proyek nyata—sebuah API manajemen tugas—yang mendemonstrasikan fundamental NestJS. Anda akan memahami bagaimana modules, controllers, services, dan decorators bekerja bersama untuk menciptakan kode yang clean dan production-grade.
Sebelum menyelam ke dalam kode, mari kita pahami mengapa NestJS penting. Aplikasi Express tradisional sering menjadi berantakan saat scale. NestJS menerapkan struktur melalui arsitektur modularnya, terinspirasi oleh Angular. Ini berarti:
Cara tercepat untuk memulai adalah menggunakan NestJS CLI:
npm install -g @nestjs/cliBuat proyek baru:
nest new task-api
cd task-apiCLI menghasilkan struktur proyek lengkap dengan semua yang Anda butuhkan. Mari kita verifikasi setup berfungsi:
npm run start:devKunjungi http://localhost:3000 dan Anda seharusnya melihat Hello World!. Itu adalah aplikasi NestJS pertama Anda yang berjalan.
task-api/
├── src/
│ ├── app.controller.ts
│ ├── app.module.ts
│ ├── app.service.ts
│ └── main.ts
├── test/
├── package.json
├── tsconfig.json
└── nest-cli.jsonSetiap file memiliki tujuan:
main.ts: Entry point aplikasiapp.module.ts: Root module yang mengorganisir aplikasi Andaapp.controller.ts: Menangani HTTP requestsapp.service.ts: Berisi business logicModules adalah containers yang mengorganisir fungsionalitas terkait. Pikirkan mereka sebagai feature domains. Sebuah module dapat memiliki controllers, services, dan modules lain sebagai dependencies.
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}Decorator @Module mendefinisikan:
imports: Module lain yang module ini bergantung padacontrollers: Request handlersproviders: Services dan injectable classes lainnyaControllers menangani incoming HTTP requests dan mengembalikan responses. Mereka menggunakan decorators untuk mendefinisikan routes dan HTTP methods.
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { AppService } from './app.service';
@Controller('tasks')
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getAllTasks() {
return this.appService.getAllTasks();
}
@Post()
createTask(@Body() createTaskDto: any) {
return this.appService.createTask(createTaskDto);
}
@Get(':id')
getTaskById(@Param('id') id: string) {
return this.appService.getTaskById(id);
}
}Key decorators:
@Controller('tasks'): Base route untuk semua methods di controller ini@Get(), @Post(): HTTP method decorators@Body(): Extract request body@Param(): Extract URL parametersServices berisi business logic dan dapat digunakan kembali di seluruh controllers. Mereka di-inject melalui constructor.
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
private tasks = [
{ id: 1, title: 'Belajar NestJS', completed: false },
{ id: 2, title: 'Bangun sebuah API', completed: false },
];
getAllTasks() {
return this.tasks;
}
createTask(createTaskDto: any) {
const newTask = {
id: this.tasks.length + 1,
...createTaskDto,
completed: false,
};
this.tasks.push(newTask);
return newTask;
}
getTaskById(id: string) {
return this.tasks.find(task => task.id === parseInt(id));
}
}Decorator @Injectable() menandai sebuah class sebagai provider yang dapat di-inject ke dalam class lain.
Sekarang mari kita bangun API task lengkap dengan struktur yang tepat. Kami akan membuat dedicated tasks module.
nest generate module tasks
nest generate controller tasks
nest generate service tasksIni membuat struktur module secara otomatis:
src/
├── tasks/
│ ├── tasks.module.ts
│ ├── tasks.controller.ts
│ └── tasks.service.tsDTOs mendefinisikan bentuk data yang masuk dan keluar. Buat src/tasks/dto/create-task.dto.ts:
export class CreateTaskDto {
title: string;
description?: string;
}import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateTaskDto } from './dto/create-task.dto';
interface Task {
id: number;
title: string;
description?: string;
completed: boolean;
createdAt: Date;
}
@Injectable()
export class TasksService {
private tasks: Task[] = [];
private idCounter = 1;
create(createTaskDto: CreateTaskDto): Task {
const task: Task = {
id: this.idCounter++,
...createTaskDto,
completed: false,
createdAt: new Date(),
};
this.tasks.push(task);
return task;
}
findAll(): Task[] {
return this.tasks;
}
findOne(id: number): Task {
const task = this.tasks.find(t => t.id === id);
if (!task) {
throw new NotFoundException(`Task dengan ID ${id} tidak ditemukan`);
}
return task;
}
update(id: number, updateTaskDto: Partial<CreateTaskDto>): Task {
const task = this.findOne(id);
Object.assign(task, updateTaskDto);
return task;
}
remove(id: number): void {
const index = this.tasks.findIndex(t => t.id === id);
if (index === -1) {
throw new NotFoundException(`Task dengan ID ${id} tidak ditemukan`);
}
this.tasks.splice(index, 1);
}
toggleComplete(id: number): Task {
const task = this.findOne(id);
task.completed = !task.completed;
return task;
}
}import {
Controller,
Get,
Post,
Body,
Param,
Delete,
Patch,
ParseIntPipe,
} from '@nestjs/common';
import { TasksService } from './tasks.service';
import { CreateTaskDto } from './dto/create-task.dto';
@Controller('tasks')
export class TasksController {
constructor(private readonly tasksService: TasksService) {}
@Post()
create(@Body() createTaskDto: CreateTaskDto) {
return this.tasksService.create(createTaskDto);
}
@Get()
findAll() {
return this.tasksService.findAll();
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.tasksService.findOne(id);
}
@Patch(':id')
update(
@Param('id', ParseIntPipe) id: number,
@Body() updateTaskDto: Partial<CreateTaskDto>,
) {
return this.tasksService.update(id, updateTaskDto);
}
@Delete(':id')
remove(@Param('id', ParseIntPipe) id: number) {
this.tasksService.remove(id);
return { message: 'Task berhasil dihapus' };
}
@Patch(':id/toggle')
toggleComplete(@Param('id', ParseIntPipe) id: number) {
return this.tasksService.toggleComplete(id);
}
}Perhatikan ParseIntPipe—ini secara otomatis mengkonversi string parameter menjadi integer dan memvalidasinya.
import { Module } from '@nestjs/common';
import { TasksService } from './tasks.service';
import { TasksController } from './tasks.controller';
@Module({
controllers: [TasksController],
providers: [TasksService],
})
export class TasksModule {}import { Module } from '@nestjs/common';
import { TasksModule } from './tasks/tasks.module';
@Module({
imports: [TasksModule],
})
export class AppModule {}Mulai development server:
npm run start:devGunakan curl atau tool seperti Bruno/Postman untuk test:
curl -X POST http://localhost:3000/tasks \
-H "Content-Type: application/json" \
-d '{"title":"Belajar NestJS","description":"Selesaikan tutorial"}'Services harus dideklarasikan di array providers module dan di-inject melalui constructor. Lupa salah satu langkah menyebabkan runtime errors.
@Module({
controllers: [TasksController],
// Missing TasksService in providers
})
export class TasksModule {}@Module({
controllers: [TasksController],
providers: [TasksService],
})
export class TasksModule {}Menerima tipe any mengalahkan manfaat TypeScript. Selalu definisikan DTOs untuk type safety dan validation.
@Post()
create(@Body() data: any) {
return this.tasksService.create(data);
}@Post()
create(@Body() createTaskDto: CreateTaskDto) {
return this.tasksService.create(createTaskDto);
}Controllers hanya harus menangani HTTP concerns. Business logic harus berada di services.
@Get()
findAll() {
const tasks = this.tasks.filter(t => !t.completed);
return tasks.sort((a, b) => a.id - b.id);
}// Di service
findAllPending(): Task[] {
return this.tasks
.filter(t => !t.completed)
.sort((a, b) => a.id - b.id);
}
// Di controller
@Get('pending')
findAllPending() {
return this.tasksService.findAllPending();
}Install class-validator dan class-transformer:
npm install class-validator class-transformerUpdate DTO Anda dengan validation decorators:
import { IsString, IsOptional, MinLength } from 'class-validator';
export class CreateTaskDto {
@IsString()
@MinLength(3)
title: string;
@IsOptional()
@IsString()
description?: string;
}Enable global validation di main.ts:
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();Struktur proyek Anda berdasarkan features, bukan berdasarkan type:
src/
├── tasks/
│ ├── dto/
│ ├── tasks.controller.ts
│ ├── tasks.service.ts
│ └── tasks.module.ts
├── users/
│ ├── dto/
│ ├── users.controller.ts
│ ├── users.service.ts
│ └── users.module.ts
└── app.module.tsIni membuat mudah untuk menemukan kode terkait dan scale aplikasi Anda.
Gunakan built-in exceptions NestJS:
import { NotFoundException, BadRequestException } from '@nestjs/common';
findOne(id: number): Task {
if (id <= 0) {
throw new BadRequestException('ID harus berupa angka positif');
}
const task = this.tasks.find(t => t.id === id);
if (!task) {
throw new NotFoundException(`Task dengan ID ${id} tidak ditemukan`);
}
return task;
}NestJS dilengkapi dengan Jest pre-configured. Buat tasks.service.spec.ts:
Jalankan tests dengan:
npm run testNestJS powerful tapi tidak selalu pilihan yang tepat:
Untuk kebanyakan REST APIs, GraphQL servers, dan backend services, NestJS adalah pilihan yang excellent.
Anda sekarang memiliki working task management API yang dibangun dengan NestJS. Anda telah mempelajari tentang modules, controllers, services, dependency injection, dan best practices. Arsitektur yang Anda bangun scales dari proyek kecil hingga aplikasi enterprise.
Langkah selanjutnya: explore dokumentasi NestJS tentang databases (TypeORM, Prisma), authentication, dan middleware. Patterns yang Anda pelajari di sini berlaku untuk semua proyek NestJS.
Mulai membangun, dan Anda akan dengan cepat melihat mengapa NestJS telah menjadi framework pilihan untuk serious Node.js backend development.