2.2 Database MongoDB
Pengenalan MongoDB
MongoDB adalah database NoSQL yang menyimpan data dalam format dokumen JSON-like yang disebut BSON (Binary JSON). Berbeda dengan database SQL tradisional, MongoDB memiliki struktur yang lebih fleksibel dan mudah untuk dikembangkan seiring berkembangnya aplikasi.
Beberapa konsep dasar MongoDB yang perlu dipahami:
Document: Unit dasar data dalam MongoDB (mirip dengan row/record di SQL)
Collection: Kumpulan documents (mirip dengan table di SQL)
Database: Kontainer untuk collections
Membuat Database dan Collection
Untuk aplikasi presensi kita, kita akan membuat database bernama attendance_db
dengan dua collection utama:
users
: Menyimpan data pengguna
attendances
: Menyimpan data presensi
Copy // Buat koneksi ke MongoDB
use attendance_db
// Collections akan dibuat secara otomatis saat pertama kali
// menyimpan document
Koneksi MongoDB dengan Mongoose
Mongoose adalah ODM (Object Data Modeling) library untuk MongoDB dan Node.js. Mari kita setup koneksi database:
Buat file config/database.js
:
Copy const mongoose = require ( 'mongoose' );
const connectDB = async () => {
try {
const conn = await mongoose .connect ( 'mongodb://localhost:27017/attendance_db' , {
useNewUrlParser : true ,
useUnifiedTopology : true
});
console .log ( `MongoDB Connected: ${ conn . connection .host } ` );
} catch (error) {
console .error ( `Error: ${ error .message } ` );
process .exit ( 1 );
}
};
module . exports = connectDB;
Membuat Schema
User Schema
Buat file models/User.js
:
Copy const mongoose = require ( 'mongoose' );
const bcrypt = require ( 'bcryptjs' );
const userSchema = new mongoose .Schema ({
name : {
type : String ,
required : [ true , 'Nama harus diisi' ] ,
trim : true
} ,
email : {
type : String ,
required : [ true , 'Email harus diisi' ] ,
unique : true ,
trim : true ,
lowercase : true ,
match : [ / ^ \w + ([.-] ? \w + ) * @\w + ([.-] ? \w + ) * (\.\w {2,3} ) +$ / , 'Email tidak valid' ]
} ,
password : {
type : String ,
required : [ true , 'Password harus diisi' ] ,
minlength : [ 6 , 'Password minimal 6 karakter' ]
} ,
role : {
type : String ,
enum : [ 'admin' , 'user' ] ,
default : 'user'
} ,
createdAt : {
type : Date ,
default : Date .now
}
});
// Hash password sebelum disimpan
userSchema .pre ( 'save' , async function (next) {
if ( ! this .isModified ( 'password' )) {
next ();
}
const salt = await bcrypt .genSalt ( 10 );
this .password = await bcrypt .hash ( this .password , salt);
});
const User = mongoose .model ( 'User' , userSchema);
module . exports = User;
Attendance Schema
Buat file models/Attendance.js
:
Copy const mongoose = require ( 'mongoose' );
const attendanceSchema = new mongoose .Schema ({
user : {
type : mongoose . Schema . Types .ObjectId ,
ref : 'User' ,
required : true
} ,
date : {
type : Date ,
required : true ,
default : Date .now
} ,
checkIn : {
time : Date ,
location : {
type : { type : String } ,
coordinates : [Number]
}
} ,
checkOut : {
time : Date ,
location : {
type : { type : String } ,
coordinates : [Number]
}
} ,
status : {
type : String ,
enum : [ 'hadir' , 'terlambat' , 'izin' , 'sakit' , 'alpha' ] ,
required : true
} ,
note : String
} , {
timestamps : true
});
// Index untuk query berdasarkan tanggal
attendanceSchema .index ({ date : 1 , user : 1 } , { unique : true });
const Attendance = mongoose .model ( 'Attendance' , attendanceSchema);
module . exports = Attendance;
CRUD Operations Dasar
Create (Membuat Data)
Copy // Membuat user baru
const createUser = async (userData) => {
try {
const user = await User .create (userData);
return user;
} catch (error) {
throw new Error ( `Error creating user: ${ error .message } ` );
}
};
// Membuat attendance baru
const createAttendance = async (attendanceData) => {
try {
const attendance = await Attendance .create (attendanceData);
return attendance;
} catch (error) {
throw new Error ( `Error creating attendance: ${ error .message } ` );
}
};
Read (Membaca Data)
Copy // Mendapatkan semua user
const getAllUsers = async () => {
try {
const users = await User .find () .select ( '-password' );
return users;
} catch (error) {
throw new Error ( `Error fetching users: ${ error .message } ` );
}
};
// Mendapatkan attendance berdasarkan user ID
const getUserAttendance = async (userId) => {
try {
const attendance = await Attendance .find ({ user : userId })
.populate ( 'user' , 'name email' )
.sort ( '-date' );
return attendance;
} catch (error) {
throw new Error ( `Error fetching attendance: ${ error .message } ` );
}
};
Update (Memperbarui Data)
Copy // Update user
const updateUser = async (userId , updateData) => {
try {
const user = await User .findByIdAndUpdate (
userId ,
updateData ,
{ new : true , runValidators : true }
);
return user;
} catch (error) {
throw new Error ( `Error updating user: ${ error .message } ` );
}
};
// Update attendance
const updateAttendance = async (attendanceId , updateData) => {
try {
const attendance = await Attendance .findByIdAndUpdate (
attendanceId ,
updateData ,
{ new : true , runValidators : true }
);
return attendance;
} catch (error) {
throw new Error ( `Error updating attendance: ${ error .message } ` );
}
};
Delete (Menghapus Data)
Copy // Hapus user
const deleteUser = async (userId) => {
try {
await User .findByIdAndDelete (userId);
// Hapus juga semua attendance terkait
await Attendance .deleteMany ({ user : userId });
return true ;
} catch (error) {
throw new Error ( `Error deleting user: ${ error .message } ` );
}
};
// Hapus attendance
const deleteAttendance = async (attendanceId) => {
try {
await Attendance .findByIdAndDelete (attendanceId);
return true ;
} catch (error) {
throw new Error ( `Error deleting attendance: ${ error .message } ` );
}
};
Penggunaan dalam API
Contoh penggunaan dalam route Express:
Copy const express = require ( 'express' );
const router = express .Router ();
const User = require ( '../models/User' );
const Attendance = require ( '../models/Attendance' );
// Create user
router .post ( '/users' , async (req , res) => {
try {
const user = await User .create ( req .body);
res .status ( 201 ) .json (user);
} catch (error) {
res .status ( 400 ) .json ({ message : error .message });
}
});
// Get user attendance
router .get ( '/attendance/:userId' , async (req , res) => {
try {
const attendance = await Attendance .find ({ user : req . params .userId })
.populate ( 'user' , 'name' )
.sort ( '-date' );
res .json (attendance);
} catch (error) {
res .status ( 400 ) .json ({ message : error .message });
}
});
Tips dan Best Practices
Selalu gunakan try-catch untuk menangani error database
Validasi data sebelum menyimpan ke database
Gunakan index untuk optimasi query
Jangan lupa untuk menutup koneksi database saat aplikasi berhenti
Batasi data yang dikembalikan (projection) untuk menghemat bandwidth
Gunakan populate dengan bijak karena dapat mempengaruhi performa
Simpan password dalam bentuk hash, jangan plain text
Gunakan timestamps untuk tracking waktu pembuatan/update document
Latihan
Buat fungsi untuk mendapatkan laporan presensi bulanan
Implementasikan fungsi search user berdasarkan nama atau email
Buat fungsi untuk mendapatkan statistik kehadiran (hadir, terlambat, izin, etc.)
Tambahkan validasi custom untuk format email
Implementasikan soft delete untuk user dan attendance