Apa itu NestJS?
Jika Anda kenal Express.js, Anda tahu itu seperti kanvas kosong ā Anda mengorganisir semuanya sendiri. NestJS adalah Express.js dengan opini dan struktur. NestJS memberi tahu Anda di mana menaruh kode, bagaimana mengorganisir module, dan menyediakan pola bawaan seperti Dependency Injection.
Analogi sederhana:
- Express.js = Setumpuk batu bata LEGO. Anda bisa membangun apapun, tapi tidak ada buku panduan.
- NestJS = Set LEGO Technic. Anda tetap punya kebebasan kreatif, tapi ada struktur terbukti untuk diikuti.
Setup Proyek
npm i -g @nestjs/cli
nest new fleet-api
cd fleet-api
Memahami Sistem Module
NestJS mengorganisir kode ke dalam modules. Setiap module adalah fitur yang mandiri. Analoginya:
Aplikasi NestJS seperti perusahaan:
āāā Perusahaan (AppModule) ā Seluruh organisasi
ā āāā Departemen HR (AuthModule) ā Menangani karyawan
ā āāā Departemen Armada (FleetModule) ā Mengelola kendaraan
ā āāā Departemen Driver (DriverModule) ā Mengelola pengemudi
ā āāā Departemen IT (SharedModule) ā Utilitas bersama
Setiap departemen (module) punya:
- Controller = Resepsionis. Menerima request dan mendelegasikan pekerjaan.
- Service = Pekerja. Berisi logika bisnis sebenarnya.
- Repository = Lemari arsip. Menangani operasi database.
Membangun Fleet Module
Langkah 1: Generate Module
nest generate module fleet
nest generate controller fleet
nest generate service fleet
Langkah 2: Definisikan Entity
// src/fleet/entities/vehicle.entity.ts
@Entity('vehicles')
export class Vehicle {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ length: 20, unique: true })
plateNumber: string;
@Column({ type: 'enum', enum: VehicleStatus, default: VehicleStatus.OFFLINE })
status: VehicleStatus;
@Column({ type: 'decimal', precision: 5, scale: 2, default: 100 })
fuelLevel: number;
@CreateDateColumn()
createdAt: Date;
}
Langkah 3: Buat DTO (Data Transfer Object)
Apa itu DTO? Ini adalah objek sederhana yang mendefinisikan data apa yang diterima API Anda. Pikirkan seperti formulir ā jika seseorang mengisi field yang salah, formulir menolaknya sebelum Anda memprosesnya.
// src/fleet/dto/create-vehicle.dto.ts
export class CreateVehicleDto {
@IsString()
@Length(3, 20)
plateNumber: string;
@IsString()
@Length(2, 100)
model: string;
@IsInt()
@Min(2000)
@Max(2030)
year: number;
}
Langkah 4: Bangun Service
// src/fleet/fleet.service.ts
@Injectable()
export class FleetService {
constructor(
@InjectRepository(Vehicle)
private readonly vehicleRepo: Repository<Vehicle>,
) {}
async findAll(): Promise<Vehicle[]> {
return this.vehicleRepo.find({ order: { plateNumber: 'ASC' } });
}
async findOne(id: string): Promise<Vehicle> {
const vehicle = await this.vehicleRepo.findOne({ where: { id } });
if (!vehicle) throw new NotFoundException(`Kendaraan dengan ID "${id}" tidak ditemukan`);
return vehicle;
}
async create(dto: CreateVehicleDto): Promise<Vehicle> {
const existing = await this.vehicleRepo.findOne({ where: { plateNumber: dto.plateNumber } });
if (existing) throw new ConflictException(`Kendaraan dengan plat "${dto.plateNumber}" sudah ada`);
return this.vehicleRepo.save(this.vehicleRepo.create(dto));
}
}
Langkah 5: Bangun Controller
@Controller('api/vehicles')
export class FleetController {
constructor(private readonly fleetService: FleetService) {}
@Get()
findAll() { return this.fleetService.findAll(); }
@Get(':id')
findOne(@Param('id', ParseUUIDPipe) id: string) { return this.fleetService.findOne(id); }
@Post()
create(@Body() dto: CreateVehicleDto) { return this.fleetService.create(dto); }
}
Memahami Dependency Injection
Ini konsep yang membingungkan kebanyakan pemula. Biar saya jelaskan dengan sederhana.
Tanpa Dependency Injection:
// ā Buruk ā service membuat dependensinya sendiri
class FleetService {
private repo = new VehicleRepository(); // Hardcoded!
}
Dengan Dependency Injection:
// ā
Baik ā dependensi disuntikkan dari luar
@Injectable()
class FleetService {
constructor(
private readonly repo: VehicleRepository, // Disuntikkan!
) {}
}
Mengapa ini penting?
- Testing ā Anda bisa menyuntikkan repository palsu untuk testing
- Fleksibilitas ā Ganti MySQL ke PostgreSQL tanpa mengubah service
- Pemisahan ā Setiap class fokus pada satu pekerjaan
Pikirkan seperti restoran. Chef (Service) tidak pergi ke ladang untuk mengambil bahan. Bahan-bahan (Dependencies) dikirimkan ke dapur. Ini membuat chef lebih efisien.
Kesalahan Umum
Kesalahan 1: Logika Bisnis di Controller
// ā Buruk ā controller terlalu banyak bekerja
@Post()
async create(@Body() dto: CreateVehicleDto) {
const existing = await this.repo.findOne({ where: { plate: dto.plate } });
if (existing) throw new ConflictException();
await this.emailService.notify('Kendaraan baru ditambahkan');
return this.repo.save(this.repo.create(dto));
}
// ā
Baik ā controller mendelegasikan ke service
@Post()
create(@Body() dto: CreateVehicleDto) { return this.fleetService.create(dto); }
Kesalahan 2: Tidak Menggunakan Pipes untuk Validasi
// ā
Baik ā gunakan pipe bawaan
@Get(':id')
findOne(@Param('id', ParseUUIDPipe) id: string) { return this.service.findOne(id); }
Selanjutnya
Di Part 4, kita akan membangun Admin Panel Laravel ā antarmuka administrasi yang powerful untuk mengelola pengemudi, invoice, dan jadwal menggunakan Laravel dan Filament.
Ini adalah Part 3 dari seri Fleet Management System. Kita membangun backend API yang bersih dan terstruktur seperti cara developer senior melakukannya.
