MongoDB is a document-oriented database: data is stored not in tables but in collections of JSON-like documents (BSON). Flexible schema, a rich query language and built-in replication.
Installation#
The official repository for Ubuntu 22.04 (MongoDB 7.0):
# Repository signing key (apt-key is deprecated — put the key into a keyring)
sudo apt install gnupg curl
curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | \
sudo gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg --dearmor
# 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
# Install and start
sudo apt update
sudo apt install -y mongodb-org
sudo systemctl enable --now mongod
Connecting to the shell (in modern versions it is mongosh; the legacy
mongo client has been removed):
mongosh
Everything below is shell commands (JavaScript). We use the customers
collection with documents shaped like { name, email, city, age, status }.
Inserting#
// One document
db.customers.insertOne({
name: "Olga Smirnova",
email: "olga@example.com",
city: "Minsk",
age: 29,
status: "active"
})
// An array of documents — batch insert
db.customers.insertMany([
{ name: "Ivan Petrov", email: "ivan@example.com", city: "Gomel", age: 34, status: "inactive" },
{ name: "Anna Koval", email: "anna@example.com", city: "Minsk", age: 41, status: "active" },
{ name: "Maksim Lee", email: "maks@example.com", city: "Brest", age: 23, status: "active" }
])
The collection is created automatically on the first insert; for a document
without an explicit _id, MongoDB generates one itself.
Queries#
Simple ones#
// All documents in the collection
db.customers.find()
// The first matching document
db.customers.findOne({ name: "Anna Koval" })
// Exact field value
db.customers.find({ city: "Minsk" })
Comparison operators#
// Age greater than 25
db.customers.find({ age: { $gt: 25 } })
// Combined range: 25 <= age <= 40
db.customers.find({ age: { $gte: 25, $lte: 40 } })
// Membership in a set of values
db.customers.find({ city: { $in: ["Minsk", "Brest"] } })
// Field exists / does not exist
db.customers.find({ phone: { $exists: true } })
// Substring at the beginning of a field (regular expression)
db.customers.find({ name: { $regex: "^A" } })
Core comparison operators: $eq, $ne, $gt, $gte, $lt, $lte,
$in, $nin, $exists, $regex.
Logical operators#
// AND: both conditions at once
db.customers.find({
age: { $gte: 25 },
status: "active"
})
// Explicit form with $and (needed when a single-field condition is complex)
db.customers.find({
$and: [
{ age: { $gte: 25 } },
{ age: { $lte: 40 } }
]
})
// OR: at least one of the conditions
db.customers.find({
$or: [
{ city: "Minsk" },
{ age: { $gte: 35 } }
]
})
// Negation of a set of conditions
db.customers.find({ $nor: [ { status: "inactive" }, { age: { $lt: 25 } } ] })
Projections, sorting and pagination#
// Only name and email (without _id) for active customers
db.customers.find(
{ status: "active" },
{ name: 1, email: 1, _id: 0 }
)
// Newest first, 10 documents, third page
db.customers.find()
.sort({ age: -1 })
.limit(10)
.skip(20)
Aggregations#
A document pipeline: every stage ($match, $group, $sort, …) processes
the stream and passes the result to the next one.
// Group by city: count and average age
db.customers.aggregate([
{ $match: { status: "active" } },
{ $group: { _id: "$city", count: { $sum: 1 }, avgAge: { $avg: "$age" } } },
{ $sort: { count: -1 } },
{ $limit: 5 }
])
Updates#
// Update one document: replace fields via $set
db.customers.updateOne(
{ name: "Ivan Petrov" },
{ $set: { status: "active" } }
)
// Update all documents matching the filter
db.customers.updateMany(
{ status: "inactive" },
{ $set: { city: "unknown" } }
)
// Increment a counter
db.customers.updateOne(
{ name: "Anna Koval" },
{ $inc: { logins: 1 } }
)
// Add an element to an array
db.customers.updateOne(
{ name: "Anna Koval" },
{ $push: { tags: "vip" } }
)
// Remove a field entirely
db.customers.updateOne(
{ name: "Anna Koval" },
{ $unset: { phone: "" } }
)
Without
$setand other operators,updateOne({ … }, { age: 30 })replaces the whole document with a new one. Always specify an operator.
A fairly common pattern is upsert: update, and if the document does not exist, insert it (handy for incremental syncs):
db.customers.updateOne(
{ email: "new@example.com" },
{ $set: { name: "New User", status: "active" } },
{ upsert: true }
)
Deletions#
// One document (the first match)
db.customers.deleteOne({ email: "maks@example.com" })
// All documents matching the filter
db.customers.deleteMany({ status: "inactive" })
// Drop the collection along with its indexes
db.customers.drop()
deleteMany({})clears documents,drop()removes the whole collection (including indexes). For "wiping a table"drop()is the faster option.
Replica set#
Replication keeps copies of the data on several nodes: a single PRIMARY accepts writes while the rest sync with it via the oplog. When the PRIMARY goes down, the members elect a new one and the cluster keeps working.
Node configuration#
The same replSetName in /etc/mongod.conf on every node. Note that
bindIp and port live in the net section, not in replication:
# /etc/mongod.conf
net:
bindIp: 0.0.0.0
port: 27017
replication:
replSetName: rs0
Then on every node: sudo systemctl restart mongod. The nodes need access to
each other and to clients on port 27017:
# Tight: only from a trusted subnet
sudo ufw allow from 10.0.0.0/24 to any port 27017
Initialization#
Connect to one node and initialize the set — the rest are listed in
members:
// Connection (the replica set is set in the connection string)
mongosh "mongodb://mongo1.example.com:27017/?replicaSet=rs0"
// Runs once, on any listed node
rs.initiate({
_id: "rs0",
members: [
{ _id: 0, host: "mongo1.example.com:27017" },
{ _id: 1, host: "mongo2.example.com:27017" },
{ _id: 2, host: "mongo3.example.com:27017" }
]
})
Status and configuration#
// State of the members: health, state (PRIMARY/SECONDARY), lag
rs.status()
// Current configuration of the set
rs.conf()
// Hand the PRIMARY role to another node (e.g., before maintenance)
rs.stepDown()
Adding and removing members#
// Attach a new node (first set the same replSetName in mongod.conf)
rs.add("mongo4.example.com:27017")
// Remove a node from the set
rs.remove("mongo3.example.com:27017")
A member's priority is set in the set configuration:
rs.conf().members— thepriorityfield; the node with the highest priority wins the PRIMARY election. A replica set needs at least three data-bearing nodes (or arbiters) — the quorum must be reliable.