Clean Code di NestJS: Biar Project Nggak Jadi Spaghetti 🍝

Pernah buka project backend sendiri, terus bingung… β€œini file ngapain, yang mana controller, yang mana logic?” 😭
Nah, berarti udah saatnya kamu belajar clean code di NestJS, biar project kamu gak kayak mie instan tanpa bumbu alias spaghetti code.

Framework secanggih apa pun gak bakal nolong kalau kodenya berantakan. Tapi kabar baiknya, NestJS itu emang didesain buat bantu kamu bikin kode yang rapi dan maintainable banget 😎

πŸ’‘ Kenapa Clean Code Penting di Backend?

Karena backend itu bukan cuma jalanin API β€” dia ngatur logika bisnis, validasi, data, sampai keamanan.
Kalau kodenya asal-asalan, begitu bug muncul… siap-siap deh debugging seharian 😩

Clean code bikin:

  • βœ… Kode mudah dibaca developer lain
  • πŸ” Fitur gampang diubah tanpa nyentuh bagian lain
  • 🧱 Project scalable dan siap di-maintain bareng tim

Intinya: semakin rapi kode kamu, semakin tenang hidup kamu nanti.

πŸ—‚οΈ Struktur Folder Ideal: By Feature vs By Layer

NestJS fleksibel banget, tapi banyak pemula salah strukturin project. Ada dua cara umum:

1. By Layer (klasik)

src/
 ┣ controllers/
 ┣ services/
 ┣ dto/
 ┣ entities/
 β”— main.ts

Cocok buat project kecil, tapi makin ribet kalau fitur udah banyak.

2. By Feature (modern & scalable)

src/
 ┣ users/
 ┃ ┣ users.controller.ts
 ┃ ┣ users.service.ts
 ┃ ┣ dto/
 ┃ β”— entities/
 ┣ auth/
 ┃ ┣ auth.controller.ts
 ┃ β”— auth.service.ts
 β”— main.ts

Setiap fitur punya folder sendiri. Mau ubah fitur auth? Tinggal buka folder auth aja β€” simple banget.
πŸ‘‰ Rekomendasi: pakai struktur β€œby feature” biar gampang scale ke microservices nanti.

πŸ’¬ Pisahkan Business Logic ke Service, Jangan di Controller

Salah satu kesalahan pemula paling sering:
numpukin logic di controller.

Controller itu cuma jembatan antara request dan response. Logic bisnis (kayak hitung total harga, kirim email, simpan ke DB) harusnya di Service.

Contoh jelek πŸ‘‡

@Get()
getAllUsers() {
  const users = this.prisma.user.findMany(); // logic disini ❌
  return users;
}

Contoh clean πŸ‘‡

@Get()
getAllUsers() {
  return this.userService.findAll(); // logic dipindah ke service βœ…
}

Service-nya:

@Injectable()
export class UserService {
  constructor(private readonly prisma: PrismaService) {}

  async findAll() {
    return await this.prisma.user.findMany();
  }
}

Jauh lebih rapi dan gampang di-maintain, kan?

πŸ“¦ Gunakan DTO (Data Transfer Object) untuk Validasi Data Masuk

DTO = format data yang kamu terima dari user.
Kalau user ngirim data yang salah (misal email kosong), langsung divalidasi sebelum masuk ke logic.

Contoh:

export class CreateUserDto {
  @IsEmail()
  email: string;

  @IsString()
  name: string;
}

Controller-nya:

@Post()
create(@Body() createUserDto: CreateUserDto) {
  return this.userService.create(createUserDto);
}

Pakai decorator kayak @IsEmail() dan @IsString() dari class-validator bikin validasi super clean dan otomatis πŸ’ͺ

⚑ Manfaatkan Pipes & Interceptors buat Jaga Konsistensi

Pipes

Pipes bisa ubah atau validasi data sebelum masuk ke controller.
Misal mau ubah string jadi number otomatis:

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

Interceptors

Kalau kamu mau log waktu eksekusi atau ubah response, pakai Interceptor.
Contoh:

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler) {
    const start = Date.now();
    return next.handle().pipe(
      tap(() => console.log(`Executed in ${Date.now() - start}ms`))
    );
  }
}

Tinggal pasang global:

app.useGlobalInterceptors(new LoggingInterceptor());

Sekarang semua request otomatis ke-log β€” clean dan reusable πŸ”₯

🧠 Tips Commit & Naming Convention untuk Tim Besar

Kalau udah kerja bareng tim, konsistensi jadi kunci.

Gunakan format commit konvensional:

feat(auth): add login endpoint
fix(user): wrong validation on email
refactor(order): simplify service logic

Gunakan penamaan yang deskriptif:

  • βœ… createUser(), findAllProducts()
  • ❌ getData(), doSomething()

Nama yang jelas bikin orang lain langsung paham konteks tanpa baca isi fungsi.

🧭 Kesimpulan: Clean Code = Maintainable System

Clean code bukan cuma soal gaya, tapi soal keberlanjutan project.
NestJS udah nyiapin semua tools β€” tinggal kamu yang pilih mau bikin kode kayak rumah rapi atau gudang berantakan πŸ˜†

✨ Ingat: Controller untuk handle request, Service untuk logic, DTO untuk validasi, Pipe & Interceptor untuk konsistensi.

Abdan Zam Zam Ramadhan
Abdan Zam Zam Ramadhan

Senior Software Engineer @ PT. Astra Internasional, Tbk.

Articles: 11