Pernah saya alami sendiri, pas lagi bikin project web app pertama saya, saya pakai MySQL dan schema-nya berantakan banget. Tiap kali ada perubahan fitur, harus alter table, tambah kolom, bikin migration, dan prayer tidak ada yang break. Terus seorang teman saya kasih saran: "coba MongoDB." Saya skeptis awalnya, tapi setelah pakai seminggu, saya ngerti kenapa banyak startup besar seperti Uber, Spotify, dan eBay pakai MongoDB untuk data mereka.
MongoDB itu database NoSQL yang simpan data dalam bentuk dokumen JSON (di MongoDB disebut BSON). Beda dengan MySQL atau PostgreSQL yang pakai tabel dan baris, MongoDB pakai collection dan document. Artinya, kamu bisa simpan data dengan struktur fleksibel tanpa harus define schema duluan di awal. Tapi bukan berarti MongoDB tidak punya aturan - kamu tetap bisa dan harus validasi data untuk aplikasi production.
Di artikel ini, saya akan ajak kamu bikin REST API CRUD lengkap dengan MongoDB, Node.js, dan Express. Dari install sampai deploy. Semua code bisa kamu copy-paste dan langsung jalan.
Apa Itu MongoDB dan Kenapa Pakai NoSQL?
MongoDB menyimpan data sebagai dokumen, bukan baris di tabel. Setiap dokumen adalah objek JSON-like dengan field dan value. Sebagai contoh, data user di MySQL butuh tabel terpisah untuk profil, alamat, dan kontak. Di MongoDB, semua itu bisa simpan dalam satu dokumen:
// Contoh dokumen user di MongoDB
{
"_id": ObjectId("64a1b2c3..."),
"nama": "Budi Santoso",
"email": "[email protected]",
"umur": 28,
"alamat": {
"kota": "Jakarta",
"provinsi": "DKI Jakarta",
"kode_pos": "10110"
},
"hobi": ["coding", "gaming", "ngopi"],
"created_at": ISODate("2026-09-21T01:00:00Z")
}
Perhatikan struktur di atas. Field alamat adalah objek nested, dan hobi adalah array. Di MySQL, kamu butuh minimal 2-3 tabel (users, alamat, hobi) plus JOIN untuk ambil data ini. Di MongoDB cukup satu query. Tapi ingat, fleksibilitas ini punya trade-off - kalau data kamu sangat relational (banyak banyak relationship antar entity), SQL database mungkin tetap lebih cocok.
Install MongoDB dan Setup Project
Pertama, install MongoDB Community Edition. Di Ubuntu:
# Import GPG key dan add repository
wget -qO - https://www.mongodb.org/static/pgp/server-7.0.asc | sudo gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg --dearmor
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu noble/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org
# Start MongoDB service
sudo systemctl start mongod
sudo systemctl enable mongod
# Cek status
sudo systemctl status mongod
Setelah MongoDB jalan, bikin folder project Node.js:
mkdir mongo-api && cd mongo-api
npm init -y
npm install express mongoose dotenv
Saya pakai mongoose sebagai ODM (Object Document Mapper). Mongoose memberi struktur dan validasi untuk data MongoDB-mu, jadi walau MongoDB schema-less, kamu tetap bisa enforce schema lewat Mongoose. dotenv untuk simpan connection string dan config lain di file .env.
Membuat Koneksi MongoDB dengan Mongoose
Bikin file .env di root project:
MONGODB_URI=mongodb://localhost:27017/blog_db
PORT=3000
Lalu bikin file db.js untuk handle koneksi:
const mongoose = require('mongoose');
async function connectDB() {
try {
await mongoose.connect(process.env.MONGODB_URI);
console.log('MongoDB connected successfully');
} catch (err) {
console.error('MongoDB connection error:', err.message);
process.exit(1);
}
}
module.exports = connectDB;
Penting: selalu pakai try-catch di sekitar koneksi. Kalau MongoDB tidak jalan atau URL salah, aplikasi kamu tidak akan crash diam-diam tanpa pesan error yang jelas.
Mendefinisikan Schema dan Model
Sekarang bikin model untuk artikel blog. Bikin folder models/ dan file Article.js:
const mongoose = require('mongoose');
const articleSchema = new mongoose.Schema({
title: {
type: String,
required: [true, 'Title wajib diisi'],
trim: true,
maxlength: 200
},
content: {
type: String,
required: true
},
author: {
type: String,
default: 'Anonymous'
},
tags: [{
type: String
}],
published: {
type: Boolean,
default: false
},
view_count: {
type: Number,
default: 0
}
}, {
timestamps: true // Otomatis bikin createdAt dan updatedAt
});
// Index untuk search performance
articleSchema.index({ title: 'text', content: 'text' });
articleSchema.index({ tags: 1 });
module.exports = mongoose.model('Article', articleSchema);
Lihat bagian timestamps: true - Mongoose otomatis bikin field createdAt dan updatedAt untuk kamu. Tidak perlu manual set tanggal setiap insert. Bagian index juga krusial - tanpa index, query search di MongoDB akan scan seluruh collection (collection scan), yang lambat banget kalau data sudah ribuan dokumen.
Bikin Express REST API - CRUD Lengkap
Sekarang bagian seringnya: bikin server Express dengan endpoint CRUD. Bikin file app.js:
require('dotenv').config();
const express = require('express');
const connectDB = require('./db');
const Article = require('./models/Article');
const app = express();
app.use(express.json());
// Connect ke MongoDB
connectDB();
// CREATE - Bikin artikel baru
app.post('/api/articles', async (req, res) => {
try {
const article = new Article(req.body);
const saved = await article.save();
res.status(201).json({
success: true,
data: saved
});
} catch (err) {
res.status(400).json({
success: false,
message: err.message
});
}
});
// READ - Ambil semua artikel
app.get('/api/articles', async (req, res) => {
try {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;
const articles = await Article.find({ published: true })
.sort({ createdAt: -1 })
.skip(skip)
.limit(limit)
.select('-__v');
const total = await Article.countDocuments({ published: true });
res.json({
success: true,
count: articles.length,
total,
page,
data: articles
});
} catch (err) {
res.status(500).json({
success: false,
message: err.message
});
}
});
// READ - Ambil satu artikel by ID
app.get('/api/articles/:id', async (req, res) => {
try {
const article = await Article.findById(req.params.id);
if (!article) {
return res.status(404).json({
success: false,
message: 'Artikel tidak ditemukan'
});
}
// Increment view count
article.view_count += 1;
await article.save();
res.json({
success: true,
data: article
});
} catch (err) {
res.status(400).json({
success: false,
message: err.message
});
}
});
// UPDATE - Edit artikel
app.put('/api/articles/:id', async (req, res) => {
try {
const updated = await Article.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true }
);
if (!updated) {
return res.status(404).json({
success: false,
message: 'Artikel tidak ditemukan'
});
}
res.json({
success: true,
data: updated
});
} catch (err) {
res.status(400).json({
success: false,
message: err.message
});
}
});
// DELETE - Hapus artikel
app.delete('/api/articles/:id', async (req, res) => {
try {
const deleted = await Article.findByIdAndDelete(req.params.id);
if (!deleted) {
return res.status(404).json({
success: false,
message: 'Artikel tidak ditemukan'
});
}
res.json({
success: true,
message: 'Artikel berhasil dihapus'
});
} catch (err) {
res.status(400).json({
success: false,
message: err.message
});
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Perhatikan beberapa hal penting di code di atas. Pertama, saya pakai async/await untuk semua database operation. Mongoose query itu Promise-based, jadi async/await bikin code lebih bersih dib chaining .then(). Kedua, setiap endpoint punya try-catch sendiri - jangan biarkan error database crash server kamu. Ketiga, endpoint GET list pakai pagination (skip dan limit) - jangan balikin seluruh collection sekaligus, itu akan memakan memory besar kalau data sudah banyak.
Test API dengan curl
Setelah server jalan (node app.js), test semua endpoint:
# Create - Bikin artikel baru
curl -X POST http://localhost:3000/api/articles \
-H "Content-Type: application/json" \
-d '{"title":"Belajar MongoDB","content":"MongoDB itu seru","author":"Saya","tags":["database","nosql"]}'
# Read - Ambil semua artikel
curl http://localhost:3000/api/articles
# Read - Ambil satu artikel (ganti ID)
curl http://localhost:3000/api/articles/64a1b2c3d4e5f6a7b8c9
# Update - Edit artikel
curl -X PUT http://localhost:3000/api/articles/64a1b2c3d4e5f6a7b8c9 \
-H "Content-Type: application/json" \
-d '{"title":"Belajar MongoDB Update","published":true}'
# Delete - Hapus artikel
curl -X DELETE http://localhost:3000/api/articles/64a1b2c3d4e5f6a7b8c9
MongoDB vs MySQL vs PostgreSQL - Mana yang Cocok?
Sering saya ditanya ini. Jawabannya: tergantung use case. Berikut perbandingan dari pengalaman saya pakai ketiganya:
- Struktur Data: Kalau data kamu relational (user punya orders, orders punya items, items punya reviews), pakai PostgreSQL/MySQL. Kalau data kamu document-oriented (artikel, komentar, log), MongoDB lebih cocok.
- Skalabilitas: MongoDB dirancang untuk horizontal scaling (sharding). Tambah server, bagi data. SQL database scaling vertikal lebih gampang (upgrade RAM/CPU).
- Schema Flexibility: MongoDB menang di sini. Bisa tambah field baru tanpa migration. Tapi ini juga bisa jadi bug kalau tidak ada validasi (itulah kenapa Mongoose penting).
- Query Complexity: JOIN kompleks? SQL menang gampang. Aggregation pipeline di MongoDB powerful tapi syntax-nya lebih panjang.
- Ecosystem: MySQL/PostgreSQL punya tools lebih matang (phpMyAdmin, pgAdmin, TablePlus). MongoDB punya Compass yang cukup bagus tapi ecosystem tooling-nya lebih kecil.
Untuk project MVP atau prototype yang schema belum fix, MongoDB sangat cepat untuk start. Kalau kamu bangun e-commerce atau financial app dengan banyak relationship, tetap pakai SQL.
Index dan Optimasi Query
Satu hal yang sering luput oleh pemula: index. Tanpa index, MongoDB scan seluruh dokumen di collection buat cari yang kamu butuh. Dengan index, query bisa 100x lebih cepat. Contoh:
// Bikin index di field yang sering di-query
articleSchema.index({ author: 1 }); // Single field index
articleSchema.index({ published: 1, createdAt: -1 }); // Compound index
articleSchema.index({ title: 'text' }); // Text index untuk search
// Cek apakah query pakai index
Article.find({ author: 'Saya' }).explain('executionStats')
// Look for "totalDocsExamined" - kalau sama dengan total collection, index tidak dipakai
Compound index itu index yang cover multiple fields. Urutan field di compound index penting - ikuti ESR rule: Equality, Sort, Range. Field yang di-filter exact match dulu, lalu field untuk sort, lalu field untuk range query.
Deploy ke Production - Tips dan Checklist
Sebelum deploy app MongoDB ke production, cek beberapa hal ini:
- Pakai MongoDB Atlas atau self-hosted? Kalau baru mulai, MongoDB Atlas (cloud managed) lebih gampang. Free tier 512MB cukup untuk MVP. Kalau self-hosted di VPS seperti DigitalOcean atau Vultr, kamu control penuh tapi harus setup backup dan security sendiri.
- Environment variables: Jangan hardcode connection string di code. Pakai
.envdan jangan commit file.envke git. - Authentication: Bikin user database khusus, jangan pakai default admin.
use blog_db; db.createUser({user:'appuser',pwd:'passkuat',roles:['readWrite']}) - Connection pooling: Mongoose sudah handle ini otomatis, tapi pastikan
maxPoolSizesesuai beban. Default 100 cukup untuk app kecil-menengah. - Backup: Setup cron job untuk
mongodumpharian. Kalau pakai Atlas, backup otomatis tersedia. - Monitoring: Pakai MongoDB Compass untuk inspect data, atau MongoExport untuk export metrics ke Prometheus/Grafana.
Tips Tambahan dari Pengalaman
1. Jangan simpan file besar di MongoDB. Gambar, video, atau file besar sebaiknya simpan di S3 atau local filesystem. MongoDB punya GridFS tapi hanya cocok untuk file < 16MB. Lebih besar dari itu, pakai object storage.
2. Hati-hati dengan N+1 query. Sama seperti SQL, kalau kamu loop dan query satu-satu, performa akan jatuh. Pakai $in operator atau Mongoose populate() untuk ambil data terkait dalam satu query.
3. Pakai projection untuk batasi field. Kalau kamu hanya butuh title dan author, jangan ambil seluruh dokumen: Article.find().select('title author'). Ini hemat bandwidth dan memory.
4. Schema validation di level database. Selain validasi di Mongoose, kamu juga bisa set validator di MongoDB sendiri dengan $jsonSchema. Ini penting kalau ada aplikasi lain yang juga nulis ke collection yang sama.
5. Test dengan data real. Jangan test hanya dengan 10 dokumen. Generate 10,000 dokumen dummy untuk test pagination dan performance. for(let i=0;i<10000;i++) Article.create({title:'Test '+i,content:'Lorem',author:'Test'})
Project Structure
Untuk project yang scalable, struktur folder saya pakai seperti ini:
mongo-api/
|-- app.js # Express server + routes
|-- db.js # MongoDB connection
|-- .env # Environment variables
|-- models/
| |-- Article.js # Article schema + model
| |-- User.js # User schema + model
|-- routes/
| |-- articles.js # Article routes
| |-- auth.js # Auth routes
|-- middleware/
| |-- errorHandler.js # Central error handler
|-- package.json
Pisahkan routes ke file sendiri pakai express.Router(). Ini bikin code lebih maintainable kalau endpoint sudah puluhan. Middleware untuk error handling juga penting - tangkap semua error di satu tempat supaya tidak ada try-catch duplikat di tiap endpoint.
Troubleshooting - Error yang Sering Muncul
Waktu pertama kali setup MongoDB, saya sempat bingung beberapa error ini. Biar kamu tidak kena hal yang sama, ini list error yang sering muncul beserta cara fix-nya:
1. MongoServerSelectionError: connect ECONNREFUSED
Ini terjadi kalau service MongoDB tidak jalan. Fix: sudo systemctl start mongod. Kalau masih error, cek log: sudo journalctl -u mongod --no-pager | tail -20. Sering penyebabnya permission issue di /var/lib/mongodb atau config file yang corrupt.
2. MongooseError: Operation buffering timed out
Mongoose queue query sebelum koneksi berhasil. Kalau MongoDB tidak terhubung dalam 60 detik, query di-queue timeout. Fix: pastikan MONGODB_URI benar dan MongoDB jalan. Tambahkan serverSelectionTimeoutMS: 5000 di options connect supaya fail lebih cepat saat development.
3. E11000 duplicate key error
Error ini muncul kalau kamu insert dokumen dengan field unique yang sudah ada. Misal: email yang sama dua kali. Fix: tambahkan unique index di schema Mongoose (email: { type: String, unique: true }) dan handle error di catch block dengan pesan yang user-friendly.
4. BSONObjTooLarge error
Setiap dokumen MongoDB max 16MB. Kalau kamu coba simpan dokumen lebih besar dari itu, error ini muncul. Solusi: pecah data jadi beberapa dokumen, atau simpan file besar di S3/object storage dan simpan hanya URL-nya di MongoDB.
5. VersionError: No matching document found
Muncul kalau kamu pakai optimistic concurrency (version key __v di Mongoose) dan dua request update dokumen yang sama secara bersamaan. Fix: gunakan findOneAndUpdate dengan filter kondisi, atau implement retry logic di middleware.
Studi Kasus: Skala dari 0 ke 100,000 Dokumen
Saya pernah bangun app blog yang dalam 6 bulan punya 100,000+ artikel. Berikut pengalaman scaling MongoDB dari nol:
Fase 1 (0-1,000 dokumen): Semua berjalan lancar tanpa index pun. Query respons cepat di bawah 50ms. MongoDB single instance di VPS 2GB RAM cukup. Tidak perlu tuning apapun.
Fase 2 (1,000-10,000): Mulai terasa lambat. Query search full-text tanpa index butuh 200-500ms. Saya tambah text index di field title dan content. Setelah index, query turun ke 5-10ms. Pagination juga jadi wajib - kalau balikin semua 10,000 dokumen sekaligus, memory VPS habis.
Fase 3 (10,000-100,000): Collection scan masih terjadi di beberapa query kompleks. Saya tambah compound index di { published: 1, createdAt: -1 } untuk listing page. RAM VPS upgrade ke 4GB. Mulai pakai explain() untuk inspect query plan dan identify query yang tidak pakai index. Total storage sekitar 2GB untuk 100,000 dokumen dengan content rata-rata 2KB per dokumen.
Lesson learned: Jangan tunggu sampai lambat baru bikin index. Tambah index sejak awal untuk field yang kamu tahu akan di-query sering. Tapi jangan over-index juga - setiap index makan storage dan slow down write operation. Rule of thumb: index field yang muncul di .find() query condition, bukan field yang cuma di-select().
Checklist Sebelum Go Live
Sebelum deploy app MongoDB ke production, pastikan kamu cek semua ini:
- Connection string pakai environment variable, bukan hardcoded
- Database user punya role
readWrite, bukandbAdminatauroot - Index sudah dibuat untuk field yang sering di-query
- Pagination di endpoint GET list (jangan balikin semua dokumen)
- Error handling di setiap endpoint (try-catch + response yang informatif)
- Rate limiting di API (pakai
express-rate-limit) untuk mencegah abuse - Backup strategy: daily
mongodumpke S3 atau pakai Atlas auto-backup - HTTPS/SSL untuk API endpoint (pakai nginx reverse proxy + Let's Encrypt)
- Logging terstruktur (pakai Winston atau Pino, bukan
console.log) - Test dengan 10,000+ dokumen dummy untuk validasi performance
Kesimpulan
MongoDB cocok banget untuk project yang butuh flexibility cepat dalam development. Dengan Mongoose, kamu dapat schema validation, type safety, dan helper functions yang bikin code lebih clean. Kuncinya: pahami kapan pakai NoSQL dan kapan tetap SQL. Jangan ikut trend - pilih tool yang sesuai problem kamu.
Kalau kamu baru mulai, saya saranan coba bikin project kecil dulu dengan MongoDB - blog API seperti di artikel ini sudah cukup untuk pahami konsep dasar. Setelah itu, pelajari aggregation pipeline yang powerful banget untuk analitik dan reporting.
Kamu sudah pernah pakai MongoDB atau masih ragu mau pilih database apa? Ceritakan pengalaman kamu di kolom komentar, nanti kita diskusi mana yang cocok untuk project kamu.