Tutorial Lengkap MongoDB: Database NoSQL untuk Aplikasi Modern
MongoDB adalah database NoSQL document-oriented yang sangat populer untuk aplikasi modern. Dengan schema yang fleksibel dan kemampuan scaling horizontal, MongoDB cocok untuk berbagai use case dari web applications hingga big data analytics.
Apa itu MongoDB?
MongoDB menyimpan data dalam format dokumen JSON-like (BSON) yang memberikan:
- Schema flexibility: Tidak perlu mendefinisikan struktur data sebelumnya
- Horizontal scaling: Mudah di-scale dengan sharding
- High performance: Optimized untuk read/write operations
- Rich query language: Support complex queries dan aggregations
- Native replication: Built-in high availability
- Content management systems
- Real-time analytics
- Mobile applications
- IoT data storage
- E-commerce platforms
- Social media applications
Instalasi MongoDB
1. Install di Ubuntu
# Import MongoDB public GPG key
curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | \
sudo gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg --dearmor
Add MongoDB repository
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | \
sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list
Update dan install
sudo apt update
sudo apt install -y mongodb-org
Start MongoDB
sudo systemctl start mongod
sudo systemctl enable mongod
Verify installation
mongosh --eval 'db.runCommand({ connectionStatus: 1 })'
2. Install via Docker
# Pull MongoDB image
docker pull mongo:7.0
Run MongoDB container
docker run -d \
--name mongodb \
-p 27017:27017 \
-e MONGOINITDBROOTUSERNAME=admin \
-e MONGOINITDBROOTPASSWORD=password123 \
-v mongodbdata:/data/db \
mongo:7.0
Connect dengan mongosh
docker exec -it mongodb mongosh -u admin -p password123
3. MongoDB Atlas (Cloud)
Untuk production, MongoDB Atlas menyediakan managed service:
# Connection string format
connectionstring = "mongodb+srv://username:password@cluster.mongodb.net/dbname"
MongoDB Shell (mongosh)
1. Basic Commands
// Connect ke MongoDB
mongosh
// Show databases
show dbs
// Use/create database
use myapp
// Show collections
show collections
// Current database
db
// Database stats
db.stats()
// Drop database
db.dropDatabase()
2. CRUD Operations
Create (Insert):// Insert single document
db.users.insertOne({
name: "Ruby Abdullah",
email: "ruby@example.com",
age: 30,
skills: ["Python", "JavaScript", "MongoDB"],
address: {
city: "Jakarta",
country: "Indonesia"
},
createdAt: new Date()
})
// Insert multiple documents
db.users.insertMany([
{ name: "Alice", email: "alice@example.com", age: 25 },
{ name: "Bob", email: "bob@example.com", age: 35 },
{ name: "Charlie", email: "charlie@example.com", age: 28 }
])
Read (Query):
// Find all documents
db.users.find()
// Find dengan filter
db.users.find({ age: { $gt: 25 } })
// Find one document
db.users.findOne({ email: "ruby@example.com" })
// Projection (select fields)
db.users.find({}, { name: 1, email: 1, id: 0 })
// Sort
db.users.find().sort({ age: -1 }) // Descending
// Limit dan skip (pagination)
db.users.find().skip(10).limit(5)
// Count
db.users.countDocuments({ age: { $gt: 25 } })
Update:
// Update one document
db.users.updateOne(
{ email: "ruby@example.com" },
{ $set: { age: 31 } }
)
// Update many documents
db.users.updateMany(
{ age: { $lt: 30 } },
{ $set: { status: "young" } }
)
// Replace entire document
db.users.replaceOne(
{ email: "bob@example.com" },
{ name: "Bob Smith", email: "bob@example.com", age: 36 }
)
// Upsert (update or insert)
db.users.updateOne(
{ email: "new@example.com" },
{ $set: { name: "New User", age: 20 } },
{ upsert: true }
)
Delete:
// Delete one document
db.users.deleteOne({ email: "alice@example.com" })
// Delete many documents
db.users.deleteMany({ age: { $lt: 25 } })
// Delete all documents in collection
db.users.deleteMany({})
3. Query Operators
Comparison:// Equal
db.users.find({ age: 30 })
// Not equal
db.users.find({ age: { $ne: 30 } })
// Greater than / less than
db.users.find({ age: { $gt: 25, $lt: 40 } })
// Greater/less than or equal
db.users.find({ age: { $gte: 25, $lte: 40 } })
// In array of values
db.users.find({ age: { $in: [25, 30, 35] } })
// Not in array
db.users.find({ age: { $nin: [25, 30] } })
Logical:
// AND (implicit)
db.users.find({ age: { $gt: 25 }, status: "active" })
// AND (explicit)
db.users.find({
$and: [
{ age: { $gt: 25 } },
{ status: "active" }
]
})
// OR
db.users.find({
$or: [
{ age: { $lt: 25 } },
{ status: "premium" }
]
})
// NOT
db.users.find({
age: { $not: { $gt: 30 } }
})
// NOR (neither)
db.users.find({
$nor: [
{ age: { $lt: 25 } },
{ status: "inactive" }
]
})
Array:
// Match array element
db.users.find({ skills: "Python" })
// Match all elements
db.users.find({ skills: { $all: ["Python", "MongoDB"] } })
// Array size
db.users.find({ skills: { $size: 3 } })
// Element match (untuk array of objects)
db.orders.find({
items: {
$elemMatch: {
product: "Laptop",
quantity: { $gt: 1 }
}
}
})
Element:
// Field exists
db.users.find({ phone: { $exists: true } })
// Field type
db.users.find({ age: { $type: "int" } })
Text Search:
// Create text index first
db.articles.createIndex({ title: "text", content: "text" })
// Text search
db.articles.find({ $text: { $search: "mongodb tutorial" } })
// Text search dengan score
db.articles.find(
{ $text: { $search: "mongodb" } },
{ score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } })
Indexes
1. Tipe Index
// Single field index
db.users.createIndex({ email: 1 }) // 1 = ascending, -1 = descending
// Compound index
db.users.createIndex({ lastName: 1, firstName: 1 })
// Unique index
db.users.createIndex({ email: 1 }, { unique: true })
// Text index
db.articles.createIndex({ content: "text" })
// Geospatial index
db.places.createIndex({ location: "2dsphere" })
// TTL index (auto-delete after time)
db.sessions.createIndex({ createdAt: 1 }, { expireAfterSeconds: 3600 })
2. Index Management
// List indexes
db.users.getIndexes()
// Drop index
db.users.dropIndex("email1")
// Drop all indexes (except id)
db.users.dropIndexes()
// Explain query (check index usage)
db.users.find({ email: "ruby@example.com" }).explain("executionStats")
Aggregation Framework
1. Basic Aggregation
// Count by field
db.users.aggregate([
{ $group: { id: "$status", count: { $sum: 1 } } }
])
// Average
db.users.aggregate([
{ $group: { id: null, avgAge: { $avg: "$age" } } }
])
// Multiple aggregations
db.orders.aggregate([
{ $match: { status: "completed" } },
{
$group: {
id: "$customerId",
totalOrders: { $sum: 1 },
totalSpent: { $sum: "$total" },
avgOrderValue: { $avg: "$total" }
}
},
{ $sort: { totalSpent: -1 } },
{ $limit: 10 }
])
2. Aggregation Stages
// $match - Filter documents
{ $match: { status: "active" } }
// $group - Group dan aggregate
{
$group: {
id: "$category",
total: { $sum: "$price" },
avg: { $avg: "$price" },
min: { $min: "$price" },
max: { $max: "$price" },
count: { $sum: 1 }
}
}
// $project - Reshape documents
{
$project: {
name: 1,
fullName: { $concat: ["$firstName", " ", "$lastName"] },
yearJoined: { $year: "$createdAt" }
}
}
// $sort - Sort results
{ $sort: { total: -1, name: 1 } }
// $limit dan $skip
{ $skip: 10 }
{ $limit: 5 }
// $unwind - Deconstruct array
{ $unwind: "$items" }
// $lookup - Join collections
{
$lookup: {
from: "products",
localField: "productId",
foreignField: "id",
as: "productDetails"
}
}
// $addFields - Add new fields
{
$addFields: {
totalWithTax: { $multiply: ["$total", 1.1] }
}
}
// $facet - Multiple pipelines
{
$facet: {
byCategory: [
{ $group: { id: "$category", count: { $sum: 1 } } }
],
byStatus: [
{ $group: { id: "$status", count: { $sum: 1 } } }
],
totalCount: [
{ $count: "total" }
]
}
}
3. Real-world Aggregation Examples
// E-commerce: Top selling products
db.orders.aggregate([
{ $unwind: "$items" },
{
$group: {
id: "$items.productId",
totalSold: { $sum: "$items.quantity" },
revenue: { $sum: { $multiply: ["$items.price", "$items.quantity"] } }
}
},
{
$lookup: {
from: "products",
localField: "id",
foreignField: "id",
as: "product"
}
},
{ $unwind: "$product" },
{
$project: {
productName: "$product.name",
totalSold: 1,
revenue: 1
}
},
{ $sort: { revenue: -1 } },
{ $limit: 10 }
])
// User activity report
db.events.aggregate([
{
$match: {
timestamp: {
$gte: ISODate("2024-01-01"),
$lt: ISODate("2024-02-01")
}
}
},
{
$group: {
id: {
userId: "$userId",
day: { $dateToString: { format: "%Y-%m-%d", date: "$timestamp" } }
},
eventCount: { $sum: 1 },
uniqueActions: { $addToSet: "$action" }
}
},
{
$group: {
id: "$id.userId",
activeDays: { $sum: 1 },
totalEvents: { $sum: "$eventCount" },
avgEventsPerDay: { $avg: "$eventCount" }
}
},
{ $sort: { totalEvents: -1 } }
])
Python dengan PyMongo
1. Setup dan Connection
from pymongo import MongoClient
from pymongo.errors import ConnectionFailure
from bson.objectid import ObjectId
from datetime import datetime
Basic connection
client = MongoClient("mongodb://localhost:27017/")
Connection dengan authentication
client = MongoClient(
"mongodb://admin:password123@localhost:27017/",
authSource="admin"
)
MongoDB Atlas connection
client = MongoClient(
"mongodb+srv://user:password@cluster.mongodb.net/?retryWrites=true&w=majority"
)
Test connection
try:
client.admin.command('ping')
print("Connected successfully!")
except ConnectionFailure:
print("Connection failed!")
Select database dan collection
db = client["myapp"]
users = db["users"]
2. CRUD dengan PyMongo
from pymongo import MongoClient
from bson.objectid import ObjectId
from datetime import datetime
client = MongoClient("mongodb://localhost:27017/")
db = client["myapp"]
users = db["users"]
Insert
def createuser(name, email, age):
user = {
"name": name,
"email": email,
"age": age,
"createdat": datetime.utcnow()
}
result = users.insertone(user)
return result.insertedid
Insert many
def createusers(userlist):
result = users.insertmany(userlist)
return result.insertedids
Read
def getuserbyemail(email):
return users.findone({"email": email})
def getusers(filters=None, limit=10, skip=0):
query = filters or {}
return list(users.find(query).skip(skip).limit(limit))
def getuserbyid(userid):
return users.findone({"id": ObjectId(userid)})
Update
def updateuser(userid, updates):
result = users.updateone(
{"id": ObjectId(userid)},
{"$set": updates}
)
return result.modifiedcount
def incrementage(userid):
result = users.updateone(
{"id": ObjectId(userid)},
{"$inc": {"age": 1}}
)
return result.modifiedcount
Delete
def deleteuser(userid):
result = users.deleteone({"id": ObjectId(userid)})
return result.deletedcount
Usage
userid = createuser("Ruby", "ruby@example.com", 30)
print(f"Created user: {userid}")
user = getuserbyemail("ruby@example.com")
print(f"Found user: {user}")
updateuser(userid, {"age": 31, "status": "active"})
3. Aggregation dengan PyMongo
from pymongo import MongoClient
client = MongoClient("mongodb://localhost:27017/")
db = client["ecommerce"]
orders = db["orders"]
Aggregation pipeline
def getsalesbycategory():
pipeline = [
{"$unwind": "$items"},
{
"$group": {
"id": "$items.category",
"totalsales": {"$sum": {"$multiply": ["$items.price", "$items.quantity"]}},
"totalitems": {"$sum": "$items.quantity"},
"ordercount": {"$sum": 1}
}
},
{"$sort": {"totalsales": -1}}
]
return list(orders.aggregate(pipeline))
Customer analytics
def gettopcustomers(limit=10):
pipeline = [
{"$match": {"status": "completed"}},
{
"$group": {
"id": "$customerid",
"totalspent": {"$sum": "$total"},
"ordercount": {"$sum": 1},
"avgordervalue": {"$avg": "$total"}
}
},
{"$sort": {"totalspent": -1}},
{"$limit": limit},
{
"$lookup": {
"from": "customers",
"localField": "id",
"foreignField": "id",
"as": "customerinfo"
}
},
{"$unwind": "$customerinfo"},
{
"$project": {
"customername": "$customerinfo.name",
"email": "$customerinfo.email",
"totalspent": 1,
"ordercount": 1,
"avgordervalue": {"$round": ["$avgordervalue", 2]}
}
}
]
return list(orders.aggregate(pipeline))
Time series analysis
def getdailyrevenue(startdate, enddate):
pipeline = [
{
"$match": {
"createdat": {"$gte": startdate, "$lt": enddate},
"status": "completed"
}
},
{
"$group": {
"id": {
"$dateToString": {"format": "%Y-%m-%d", "date": "$createdat"}
},
"revenue": {"$sum": "$total"},
"orders": {"$sum": 1}
}
},
{"$sort": {"id": 1}}
]
return list(orders.aggregate(pipeline))
4. Index Management
from pymongo import MongoClient, ASCENDING, DESCENDING, TEXT
client = MongoClient("mongodb://localhost:27017/")
db = client["myapp"]
users = db["users"]
Create indexes
users.createindex("email", unique=True)
users.createindex([("lastname", ASCENDING), ("firstname", ASCENDING)])
users.createindex([("location", "2dsphere")])
users.createindex([("bio", TEXT), ("interests", TEXT)])
Create TTL index
sessions = db["sessions"]
sessions.createindex("createdat", expireAfterSeconds=3600)
List indexes
for index in users.listindexes():
print(index)
Drop index
users.dropindex("email1")
Motor: Async MongoDB Driver
import asyncio
from motor.motorasyncio import AsyncIOMotorClient
async def main():
# Connect
client = AsyncIOMotorClient("mongodb://localhost:27017/")
db = client["myapp"]
users = db["users"]
# Insert
result = await users.insertone({
"name": "Async User",
"email": "async@example.com"
})
print(f"Inserted: {result.insertedid}")
# Find
user = await users.findone({"email": "async@example.com"})
print(f"Found: {user}")
# Find many
cursor = users.find({"status": "active"})
async for doc in cursor:
print(doc)
# Aggregation
pipeline = [
{"$group": {"id": "$status", "count": {"$sum": 1}}}
]
cursor = users.aggregate(pipeline)
async for doc in cursor:
print(doc)
asyncio.run(main())
FastAPI dengan MongoDB
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from typing import Optional, List
from motor.motorasyncio import AsyncIOMotorClient
from bson import ObjectId
from datetime import datetime
app = FastAPI()
MongoDB connection
client = AsyncIOMotorClient("mongodb://localhost:27017/")
db = client["myapp"]
Pydantic models
class PyObjectId(ObjectId):
@classmethod
def _getvalidators_(cls):
yield cls.validate
@classmethod
def validate(cls, v, handler):
if not ObjectId.isvalid(v):
raise ValueError("Invalid ObjectId")
return ObjectId(v)
class UserCreate(BaseModel):
name: str
email: str
age: int
class UserResponse(BaseModel):
id: str = Field(alias="id")
name: str
email: str
age: int
createdat: datetime
class Config:
populatebyname = True
jsonencoders = {ObjectId: str}
Routes
@app.post("/users", responsemodel=UserResponse)
async def createuser(user: UserCreate):
userdict = user.modeldump()
userdict["createdat"] = datetime.utcnow()
result = await db.users.insertone(userdict)
created = await db.users.findone({"id": result.insertedid})
created["id"] = str(created["id"])
return created
@app.get("/users", responsemodel=List[UserResponse])
async def getusers(skip: int = 0, limit: int = 10):
cursor = db.users.find().skip(skip).limit(limit)
users = await cursor.tolist(length=limit)
for user in users:
user["id"] = str(user["id"])
return users
@app.get("/users/{userid}", responsemodel=UserResponse)
async def getuser(userid: str):
user = await db.users.findone({"id": ObjectId(userid)})
if not user:
raise HTTPException(statuscode=404, detail="User not found")
user["id"] = str(user["id"])
return user
@app.put("/users/{userid}", responsemodel=UserResponse)
async def updateuser(userid: str, user: UserCreate):
result = await db.users.updateone(
{"id": ObjectId(userid)},
{"$set": user.modeldump()}
)
if result.modifiedcount == 0:
raise HTTPException(statuscode=404, detail="User not found")
updated = await db.users.findone({"id": ObjectId(userid)})
updated["id"] = str(updated["id"])
return updated
@app.delete("/users/{userid}")
async def deleteuser(userid: str):
result = await db.users.deleteone({"id": ObjectId(userid)})
if result.deletedcount == 0:
raise HTTPException(statuscode=404, detail="User not found")
return {"message": "User deleted"}
Run: uvicorn main:app --reload
Best Practices
1. Schema Design
# Embedding vs Referencing
Embedding (untuk data yang selalu diakses bersama)
userwithaddress = {
"name": "Ruby",
"email": "ruby@example.com",
"addresses": [
{"type": "home", "city": "Jakarta", "country": "Indonesia"},
{"type": "work", "city": "Bandung", "country": "Indonesia"}
]
}
Referencing (untuk data yang besar atau sering di-update)
user = {
"name": "Ruby",
"email": "ruby@example.com"
}
order = {
"userid": ObjectId("..."), # Reference ke user
"items": [...],
"total": 100
}
2. Connection Management
from pymongo import MongoClient
from contextlib import contextmanager
Connection pooling
client = MongoClient(
"mongodb://localhost:27017/",
maxPoolSize=50,
minPoolSize=10,
maxIdleTimeMS=30000
)
Context manager untuk transactions
@contextmanager
def getsession():
with client.startsession() as session:
yield session
Usage dengan transaction
with getsession() as session:
with session.starttransaction():
db.users.insertone({"name": "Test"}, session=session)
db.logs.insertone({"action": "usercreated"}, session=session)
3. Error Handling
from pymongo.errors import (
ConnectionFailure,
DuplicateKeyError,
WriteError
)
def safeinsert(collection, document):
try:
result = collection.insertone(document)
return result.insertedid
except DuplicateKeyError:
print("Document already exists")
return None
except WriteError as e:
print(f"Write error: {e}")
return None
except ConnectionFailure:
print("Connection failed")
return None
4. Performance Tips
# Use projection untuk mengurangi data transfer
users.find({}, {"name": 1, "email": 1})
Batch operations
from pymongo import InsertOne, UpdateOne
operations = [
InsertOne({"name": "User 1"}),
InsertOne({"name": "User 2"}),
UpdateOne({"name": "User 1"}, {"$set": {"status": "active"}})
]
result = users.bulkwrite(operations)
Create appropriate indexes
users.createindex([("email", 1)], unique=True)
users.createindex([("status", 1), ("created_at", -1)])
Kesimpulan
MongoDB adalah database NoSQL yang powerful dan fleksibel untuk aplikasi modern. Key takeaways:
Tips penggunaan:
- Design schema berdasarkan access patterns
- Gunakan indexes untuk query yang sering digunakan
- Leverage aggregation framework untuk analytics
- Implement proper connection pooling
- Handle errors gracefully