Framework 21 Jul 2026 88 views 0 komentar

NestJS untuk Backend Developer - Framework TypeScript yang Bikin Kode Lebih Rapi

NestJS untuk Backend Developer - Framework TypeScript yang Bikin Kode Lebih Rapi

Pernah nggak sih kamu ngerasa kode backend Node.js kamu mulai berantakan waktu project makin besar? Saya pernah banget. Dulu saya pakai Express.js untuk semua project, tapi begitu tim mulai grow dan kodebase membengkak, semuanya jadi susah di-maintain. Folder structure nggak jelas, dependency injection manual, dan testing? Lupakan.

Lalu saya coba NestJS. Dan honestly, ini game-changer buat saya. Framework ini bikin kode backend TypeScript saya jadi super rapi, terstruktur, dan gampang di-test. Kalau kamu developer yang udah capek dengan "Express chaos", artikel ini cocok buat kamu.

Apa Itu NestJS?

NestJS adalah framework Node.js yang dibangun di atas TypeScript. Dirikan oleh Kamil My liwiec pada tahun 2017, framework ini terinspirasi dari Angular jadi kalau kamu pernah pakai Angular, kamu bakal langsung familiar dengan konsepnya.

Yang bikin NestJS beda dari Express atau Fastify adalah arsitekturnya yang heavily opinionated. NestJS pakai pola dependency injection, decorators, dan modular architecture yang bikin kode kamu konsisten dari hari pertama.

Di balik hood, NestJS bisa pakai Express atau Fastify sebagai HTTP engine. Jadi kamu tetap bisa pakai middleware Express yang udah ada, tapi dengan struktur yang jauh lebih baik.

Kenapa Harus Pakai NestJS?

Ada beberapa alasan kenapa saya akhirnya switch ke NestJS untuk hampir semua project backend:

  • TypeScript First NestJS dibangun dari ground up dengan TypeScript. Autocomplete, type safety, dan error detection sebelum runtime. Nggak perlu setup manual lagi.
  • Modular Architecture Setiap fitur dipecah jadi module yang independen. Mau tambah feature baru? Buat module baru, selesai.
  • Dependency Injection Nggak perlu lagi bikin singleton manual atau passing dependencies lewat constructor satu per satu. NestJS handle semuanya otomatis.
  • Built-in Testing Karena DI-nya kuat, unit testing jadi gampang banget. Mocking dependency? Tinggal override provider.
  • CLI yang Powerful nest generate bisa bikin controller, service, module, guard, interceptor semuanya dengan satu command.

Installasi dan Setup Project Pertama

Sebelum mulai, pastikan kamu udah punya Node.js versi 18+ dan npm terinstall. Setup NestJS itu gampang banget:

# Install NestJS CLI globally
npm install -g @nestjs/cli

# Buat project baru
nest new my-backend-app

# Masuk ke folder project
cd my-backend-app

# Jalankan development server
npm run start:dev

Begitu kamu jalankan npm run start:dev, server bakal jalan di http://localhost:3000 dengan hot-reload. Setiap kali kamu save file, server otomatis restart.

Struktur folder yang dihasilkan CLI:

my-backend-app/
  src/
      app.controller.ts      # Controller utama
      app.controller.spec.ts # Unit test
      app.module.ts           # Root module
      app.service.ts          # Service utama
      main.ts                 # Entry point
  test/
  package.json
  tsconfig.json
  nest-cli.json

Memahami Konsep Dasar: Module, Controller, Service

NestJS punya tiga building block utama yang wajib kamu pahami:

Module adalah container untuk fitur terkait. Setiap aplikasi NestJS minimal punya satu root module (AppModule). Kamu bisa bikin module tambahan untuk setiap fitur misalnya UsersModule, AuthModule, ProductsModule.

Controller menangani incoming request dan mengembalikan response. Di sinilah kamu define route handlers sama kayak di Express, tapi pakai decorator.

Service berisi business logic. Controller cuma menerima request dan delegate ke service. Ini bikin kode tetap bersih dan testable.

Mari kita bikin contoh CRUD sederhana untuk manage produk:

# Generate module, controller, dan service sekaligus
nest generate module products
nest generate controller products
nest generate service products

Command di atas otomatis bikin tiga file dan registrasi module-nya di AppModule. Sekarang kita isi kode-nya:

// src/products/dto/create-product.dto.ts
export class CreateProductDto {
  name: string;
  price: number;
  description?: string;
  category: string;
}
// src/products/products.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateProductDto } from './dto/create-product.dto';

export interface Product {
  id: number;
  name: string;
  price: number;
  description: string;
  category: string;
}

@Injectable()
export class ProductsService {
  private products: Product[] = [];
  private idCounter = 1;

  create(dto: CreateProductDto): Product {
    const product: Product = {
      id: this.idCounter++,
      name: dto.name,
      price: dto.price,
      description: dto.description || '',
      category: dto.category,
    };
    this.products.push(product);
    return product;
  }

  findAll(category?: string): Product[] {
    if (category) {
      return this.products.filter(p => p.category === category);
    }
    return this.products;
  }

  findOne(id: number): Product {
    const product = this.products.find(p => p.id === id);
    if (!product) {
      throw new NotFoundException(`Produk dengan ID ${id} tidak ditemukan`);
    }
    return product;
  }

  remove(id: number): void {
    const index = this.products.findIndex(p => p.id === id);
    if (index === -1) {
      throw new NotFoundException(`Produk dengan ID ${id} tidak ditemukan`);
    }
    this.products.splice(index, 1);
  }
}
// src/products/products.controller.ts
import {
  Controller, Get, Post, Delete,
  Body, Param, Query, ParseIntPipe,
} from '@nestjs/common';
import { ProductsService } from './products.service';
import { CreateProductDto } from './dto/create-product.dto';

@Controller('products')
export class ProductsController {
  constructor(private readonly productsService: ProductsService) {}

  @Post()
  create(@Body() dto: CreateProductDto) {
    return this.productsService.create(dto);
  }

  @Get()
  findAll(@Query('category') category?: string) {
    return this.productsService.findAll(category);
  }

  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return this.productsService.findOne(id);
  }

  @Delete(':id')
  remove(@Param('id', ParseIntPipe) id: number) {
    this.productsService.remove(id);
    return { message: 'Produk berhasil dihapus' };
  }
}

Lihat bagaimana controller-nya super clean? Nggak ada business logic sama sekali di sana. Semua dikerjain oleh service. Dan ParseIntPipe otomatis validasi bahwa parameter id adalah angka kalau bukan, NestJS langsung return 400 Bad Request.

Dependency Injection: Magic di Balik Layar

Salah satu fitur terkuat NestJS adalah dependency injection. Lihat kode controller di atas constructor-nya cuma satu parameter: private readonly productsService: ProductsService. Kamu nggak perlu manual bikin instance, nggak perlu passing dependencies. NestJS handle semuanya.

Ini bikin testing jadi gampang banget:

// src/products/products.service.spec.ts
import { ProductsService } from './products.service';
import { NotFoundException } from '@nestjs/common';

describe('ProductsService', () => {
  let service: ProductsService;

  beforeEach(() => {
    service = new ProductsService();
  });

  it('should create a product', () => {
    const product = service.create({
      name: 'Laptop ASUS',
      price: 15000000,
      category: 'hardware',
    });
    expect(product.id).toBe(1);
    expect(product.name).toBe('Laptop ASUS');
  });

  it('should throw NotFoundException for invalid ID', () => {
    expect(() => service.findOne(999)).toThrow(NotFoundException);
  });

  it('should filter products by category', () => {
    service.create({ name: 'Laptop', price: 15000000, category: 'hardware' });
    service.create({ name: 'Mouse', price: 200000, category: 'accessories' });
    const hardware = service.findAll('hardware');
    expect(hardware.length).toBe(1);
    expect(hardware[0].category).toBe('hardware');
  });
});

Guards dan Interceptors: Middleware Level Lanjut

NestJS punya beberapa fitur advanced yang bikin kamu nggak perlu nulis boilerplate code:

Guards untuk authentication dan authorization. Misalnya kamu mau proteksi endpoint tertentu supaya cuma bisa diakses user yang login:

// src/auth/auth.guard.ts
import {
  CanActivate, ExecutionContext,
  Injectable, UnauthorizedException,
} from '@nestjs/common';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const token = request.headers.authorization?.split(' ')[1];

    if (!token) {
      throw new UnauthorizedException('Token tidak ditemukan');
    }

    // Validasi token di sini (JWT, API key, dll)
    // Contoh sederhana:
    if (token !== 'valid-api-key') {
      throw new UnauthorizedException('Token tidak valid');
    }

    return true;
  }
}

Pakai guard-nya gampang banget tinggal tambah decorator:

@UseGuards(AuthGuard)
@Get('admin/products')
findAllAdmin() {
  return this.productsService.findAll();
}

Interceptors untuk transform response, logging, atau caching. Misalnya kamu mau tambah response time ke setiap API response:

// src/common/interceptors/timing.interceptor.ts
import {
  CallHandler, ExecutionContext,
  Injectable, NestInterceptor,
} from '@nestjs/common';
import { Observable, tap } from 'rxjs';

@Injectable()
export class TimingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const start = Date.now();
    return next.handle().pipe(
      tap(() => {
        const elapsed = Date.now() - start;
        console.log(`Request selesai dalam ${elapsed}ms`);
      }),
    );
  }
}

Integrasi dengan Database: TypeORM + PostgreSQL

Untuk project production, kamu pasti butuh database. NestJS punya integrasi yang bagus dengan berbagai ORM. Saya paling sering pakai TypeORM karena support TypeScript dengan baik.

# Install dependencies
npm install @nestjs/typeorm typeorm pg
// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ProductsModule } from './products/products.module';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: 'localhost',
      port: 5432,
      username: 'postgres',
      password: 'password',
      database: 'myapp',
      autoLoadEntities: true,
      synchronize: true, // JANGAN pakai di production!
    }),
    ProductsModule,
  ],
})
export class AppModule {}

Setelah itu, ubah entity dari in-memory array jadi real database entity:

// src/products/product.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Product {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column('decimal')
  price: number;

  @Column({ default: '' })
  description: string;

  @Column()
  category: string;

  @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
  createdAt: Date;
}

Service-nya berubah jadi lebih sederhana karena TypeORM handle semua query:

// src/products/products.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Product } from './product.entity';
import { CreateProductDto } from './dto/create-product.dto';

@Injectable()
export class ProductsService {
  constructor(
    @InjectRepository(Product)
    private productsRepo: Repository<Product>,
  ) {}

  create(dto: CreateProductDto): Promise<Product> {
    const product = this.productsRepo.create(dto);
    return this.productsRepo.save(product);
  }

  findAll(category?: string): Promise<Product[]> {
    if (category) {
      return this.productsRepo.find({ where: { category } });
    }
    return this.productsRepo.find();
  }

  async findOne(id: number): Promise<Product> {
    const product = await this.productsRepo.findOneBy({ id });
    if (!product) {
      throw new NotFoundException(`Produk ID ${id} tidak ditemukan`);
    }
    return product;
  }

  async remove(id: number): Promise<void> {
    const result = await this.productsRepo.delete(id);
    if (result.affected === 0) {
      throw new NotFoundException(`Produk ID ${id} tidak ditemukan`);
    }
  }
}

Deploy ke Production

NestJS gampang banget di-deploy. Build dulu, lalu jalankan hasilnya:

# Build untuk production
npm run build

# Jalankan hasil build
node dist/main.js

# Atau pakai PM2 untuk process management
npm install -g pm2
pm2 start dist/main.js --name my-backend

Kalau kamu pakai Docker, bikin Dockerfile sederhana:

FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY dist/ ./dist/
EXPOSE 3000
CMD ["node", "dist/main.js"]

Kesimpulan

NestJS bukan framework yang bikin kamu coding lebih cepat di awal ada learning curve yang harus kamu lewati. Tapi begitu kamu paham konsepnya (Module, Controller, Service, DI), produktivitas kamu naik drastis. Kode jadi lebih konsisten, gampah di-test, dan gampah di-maintain bahkan dengan tim yang besar.

Untuk kamu yang udah lama pakai Express dan mulai ngerasa kodenya berantakan, NestJS adalah natural next step. Kamu tetap bisa pakai semua middleware Express yang udah ada, tapi dengan arsitektur yang jauh lebih baik.

Kalau ada pertanyaan soal NestJS atau pengalaman migrasi dari Express, drop a comment di bawah. Saya senang sharing pengalaman soal ini!


Bagikan artikel ini:

Komentar (0)

Belum ada komentar. Jadilah yang pertama memberikan tanggapan!

Tinggalkan Komentar