2.2 Database MongoDB - Tutorial Aplikasi Presensi

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

// 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:

  1. Install Mongoose:

npm install mongoose
  1. Buat file config/database.js:

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:

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:

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)

// 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)

// 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)

// 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)

// 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:

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

  1. Selalu gunakan try-catch untuk menangani error database

  2. Validasi data sebelum menyimpan ke database

  3. Gunakan index untuk optimasi query

  4. Jangan lupa untuk menutup koneksi database saat aplikasi berhenti

  5. Batasi data yang dikembalikan (projection) untuk menghemat bandwidth

  6. Gunakan populate dengan bijak karena dapat mempengaruhi performa

  7. Simpan password dalam bentuk hash, jangan plain text

  8. Gunakan timestamps untuk tracking waktu pembuatan/update document

Latihan

  1. Buat fungsi untuk mendapatkan laporan presensi bulanan

  2. Implementasikan fungsi search user berdasarkan nama atau email

  3. Buat fungsi untuk mendapatkan statistik kehadiran (hadir, terlambat, izin, etc.)

  4. Tambahkan validasi custom untuk format email

  5. Implementasikan soft delete untuk user dan attendance

Last updated