MongoDB Database Setup for Web Application
MongoDB is justified where data schema frequently changes, documents are difficult to normalize, or when flexible search is needed across nested structures without JOINs. Product catalogs with arbitrary attributes, content management systems, analytics event collection — typical use cases.
Installing MongoDB 7.0
# Ubuntu 22.04 / 24.04
curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | gpg --dearmor -o /usr/share/keyrings/mongodb-server-7.0.gpg
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" > /etc/apt/sources.list.d/mongodb-org-7.0.list
apt update && apt install -y mongodb-org
systemctl enable mongod && systemctl start mongod
Initial security setup:
// mongosh
use admin
db.createUser({
user: "admin",
pwd: "strong_admin_password",
roles: ["root"]
})
// Create application user
use myapp
db.createUser({
user: "myapp",
pwd: "app_password",
roles: [{ role: "readWrite", db: "myapp" }]
})
/etc/mongod.conf
net:
port: 27017
bindIp: 127.0.0.1 # localhost only; for replication — specify IP
security:
authorization: enabled
storage:
dbPath: /var/lib/mongodb
wiredTiger:
engineConfig:
cacheSizeGB: 2 # 50% available RAM for WiredTiger cache
operationProfiling:
slowOpThresholdMs: 100
mode: slowOp
replication:
replSetName: "rs0" # enable if replication is needed
Indexes
// Primary indexes — create immediately during design
db.users.createIndex({ email: 1 }, { unique: true, background: true })
db.orders.createIndex({ userId: 1, createdAt: -1 })
db.orders.createIndex({ status: 1, createdAt: -1 })
// Partial index — only active documents
db.sessions.createIndex(
{ userId: 1, expiresAt: 1 },
{ partialFilterExpression: { revokedAt: { $exists: false } } }
)
// TTL index — auto-delete outdated documents
db.logs.createIndex({ createdAt: 1 }, { expireAfterSeconds: 2592000 }) // 30 days
// Text search
db.articles.createIndex({ title: "text", body: "text" }, { default_language: "english" })
// Compound wildcard for catalog with arbitrary attributes
db.products.createIndex({ "attributes.$**": 1 })
Aggregation Pipeline
Powerful tool for analytics directly in the database:
// Revenue by categories for period
db.orders.aggregate([
{
$match: {
createdAt: { $gte: ISODate("2024-01-01"), $lt: ISODate("2024-04-01") },
status: "paid"
}
},
{ $unwind: "$items" },
{
$lookup: {
from: "products",
localField: "items.productId",
foreignField: "_id",
as: "product"
}
},
{ $unwind: "$product" },
{
$group: {
_id: "$product.category",
revenue: { $sum: { $multiply: ["$items.price", "$items.quantity"] } },
orders: { $addToSet: "$_id" }
}
},
{
$project: {
category: "$_id",
revenue: { $round: ["$revenue", 2] },
orderCount: { $size: "$orders" }
}
},
{ $sort: { revenue: -1 } }
])
Replica Set
// mongosh on one of the nodes
rs.initiate({
_id: "rs0",
members: [
{ _id: 0, host: "mongo1:27017", priority: 2 },
{ _id: 1, host: "mongo2:27017", priority: 1 },
{ _id: 2, host: "mongo3:27017", priority: 0, hidden: true, votes: 0 }
// third node — hidden for backups, doesn't participate in elections
]
})
// Check status
rs.status()
Connection string for application with failover:
mongodb://myapp:password@mongo1:27017,mongo2:27017/myapp?replicaSet=rs0&readPreference=secondaryPreferred&w=majority
Mongoose (Node.js)
import mongoose, { Schema, Document } from 'mongoose'
interface IProduct extends Document {
sku: string
name: string
category: string
price: number
attributes: Record<string, unknown>
createdAt: Date
}
const productSchema = new Schema<IProduct>({
sku: { type: String, required: true, unique: true, index: true },
name: { type: String, required: true },
category: { type: String, required: true, index: true },
price: { type: Number, required: true, min: 0 },
attributes: { type: Schema.Types.Mixed, default: {} }
}, {
timestamps: true,
toJSON: { virtuals: true }
})
productSchema.index({ name: 'text', 'attributes.description': 'text' })
export const Product = mongoose.model<IProduct>('Product', productSchema)
// Query with pagination
export async function listProducts(category: string, page = 1, limit = 24) {
const skip = (page - 1) * limit
const [items, total] = await Promise.all([
Product.find({ category }).sort({ createdAt: -1 }).skip(skip).limit(limit).lean(),
Product.countDocuments({ category })
])
return { items, total, pages: Math.ceil(total / limit) }
}
Backup
# mongodump — logical backup
mongodump --uri="mongodb://myapp:password@localhost:27017/myapp" \
--gzip --archive=/backup/myapp_$(date +%Y%m%d).archive
# Restore
mongorestore --uri="mongodb://admin:password@localhost:27017" \
--gzip --archive=/backup/myapp_20240315.archive
# mongodump single collection
mongodump --uri="..." --collection=orders --query='{"createdAt":{"$gte":{"$date":"2024-01-01T00:00:00Z"}}}'
Timeline
Setup with replica set, configuring indexes for specific schema: 1–2 days. Sharding a large cluster with data balancing: 3–5 days. Migration from relational database with schema transformation: 1–3 weeks depending on domain model complexity.







