“Kok aplikasi Node.js-ku makin lama makin nggak teratur, ya?”
Pernah merasa begitu? Tenang, kamu tidak sendiri. Banyak developer—terutama pemula—mengalami hal yang sama saat aplikasinya mulai tumbuh. Awalnya sederhana, tapi lama-lama struktur foldernya jadi berantakan, logika bisnis tercampur dengan hal teknis, dan akhirnya bikin pusing sendiri.
Nah, kabar baiknya: semua ini bisa dicegah dengan pendekatan yang disebut Clean Architecture.
Artikel ini akan membahas secara rinci dan mudah dipahami tentang:
- Apa itu Clean Architecture
- Kenapa penting diterapkan
- Bagaimana mengimplementasikannya di proyek Node.js (untuk pemula)
Apa Itu Clean Architecture?
Clean Architecture adalah arsitektur perangkat lunak yang memisahkan kode menjadi beberapa lapisan (layer) untuk menjaga agar:
- Kode tetap modular
- Bisa diuji dengan mudah (testable)
- Mudah dipelihara dan dikembangkan
Konsep ini dipopulerkan oleh Uncle Bob (Robert C. Martin) dalam bukunya Clean Architecture. Intinya: “bisnis logikamu tidak boleh tergantung pada detail teknis seperti database, framework, atau UI.”
Struktur Dasar Clean Architecture
Clean Architecture biasanya dibagi menjadi 4 layer:
1. Entities (Entitas)
- Berisi business rules atau logika inti aplikasi.
- Tidak tergantung pada library atau framework apa pun.
- Contoh: struktur data
User
, aturan validasi, dll.
2. Use Cases / Interactors
- Berisi logika aplikasi (aturan spesifik tentang apa yang aplikasi lakukan).
- Mengatur alur proses antara entitas dan luarannya.
3. Interface Adapters
- Menjembatani antara use cases dengan hal-hal eksternal seperti database, API, atau UI.
- Di sini tempatnya controller, presenter, dan repository interface.
4. Frameworks and Drivers
- Bagian paling luar: Express, MongoDB, PostgreSQL, dan library lainnya.
- Semua "detail teknis" disimpan di sini.
Contoh Struktur Folder Clean Architecture di Node.js
src/
├── entities/
│ └── User.js
├── usecases/
│ └── RegisterUser.js
├── adapters/
│ ├── controllers/
│ │ └── RegisterController.js
│ ├── repositories/
│ │ ├── UserRepository.js (interface)
│ │ └── MongoUserRepository.js (implementasi)
├── frameworks/
│ ├── express/
│ │ └── server.js
│ └── database/
│ └── mongo.js
Implementasi Sederhana: Register User
1. Entities/User.js
class User {
constructor({ name, email }) {
if (!email.includes('@')) throw new Error('Invalid email');
this.name = name;
this.email = email;
}
}
module.exports = User;
2. Usecases/RegisterUser.js
module.exports = function RegisterUser(userRepo) {
return async function (userData) {
const user = new (require('../entities/User'))(userData);
const existing = await userRepo.findByEmail(user.email);
if (existing) throw new Error('User already exists');
return await userRepo.save(user);
};
};
3. Adapters/Repositories/UserRepository.js (Interface)
module.exports = class UserRepository {
async findByEmail(email) {}
async save(user) {}
};
4. Adapters/Repositories/MongoUserRepository.js
const UserRepo = require('./UserRepository');
class MongoUserRepository extends UserRepo {
constructor(db) {
super();
this.db = db;
}
async findByEmail(email) {
return this.db.collection('users').findOne({ email });
}
async save(user) {
return this.db.collection('users').insertOne(user);
}
}
module.exports = MongoUserRepository;
5. Adapters/Controllers/RegisterController.js
module.exports = function RegisterController(registerUserUseCase) {
return async function (req, res) {
try {
const result = await registerUserUseCase(req.body);
res.status(201).json({ success: true, data: result });
} catch (err) {
res.status(400).json({ error: err.message });
}
};
};
6. Frameworks/Express/server.js
const express = require('express');
const { MongoClient } = require('mongodb');
const MongoUserRepository = require('../../adapters/repositories/MongoUserRepository');
const RegisterUser = require('../../usecases/RegisterUser');
const RegisterController = require('../../adapters/controllers/RegisterController');
const app = express();
app.use(express.json());
(async () => {
const client = await MongoClient.connect('mongodb://localhost:27017');
const db = client.db('cleanarch');
const userRepo = new MongoUserRepository(db);
const registerUser = RegisterUser(userRepo);
app.post('/register', RegisterController(registerUser));
app.listen(3000, () => console.log('Server running at http://localhost:3000'));
})();
Kenapa Clean Architecture Layak Dipakai?
- Mudah dirawat
- Terpisah dengan baik antara logika dan teknologi
- Gampang diganti: Mau pindah dari MongoDB ke PostgreSQL? Tinggal ganti implementasi repo!
- Cocok untuk tim besar atau proyek jangka panjang
- Siap untuk diuji unit test secara independen
Tantangan Clean Architecture
- Terlihat rumit di awal, apalagi untuk proyek kecil
- Perlu disiplin dalam pemisahan tanggung jawab
- Banyak file dan folder yang terlihat “berlebihan”
Tapi percayalah: di proyek besar, kamu akan sangat bersyukur memakainya.
Kesimpulan
Clean Architecture bukan sekadar gaya struktur folder, tapi cara berpikir: memisahkan logika aplikasi dari hal-hal teknis agar lebih fleksibel, teruji, dan mudah berkembang. Meski awalnya terasa rumit, tapi dengan latihan, kamu akan terbiasa.