Databases, collections & schema control
|
This section documents the current MongoDB 8.x server line as published at the MongoDB Server Manual, which is the reference these pages are written and verified against. No specific patch version is pinned. Some capabilities (Atlas Search, Atlas Vector Search, and parts of encryption and backup) are Atlas-only — they are linked, not documented in depth. This content was generated with the assistance of AI and should be verified against the official manual before being relied on in production, since MongoDB iterates quickly. This section’s bibliography lists the reference material consulted while preparing these pages. |
A MongoDB deployment holds one or more databases; each database holds collections; each collection holds BSON documents (see Documents & BSON). Neither a database nor an ordinary collection has to be declared before use, but several specialised collection types must be created explicitly to opt into fixed size, automatic expiry, a different physical layout, or a query-time pipeline.
Implicit creation on first write
Referencing a database with use does not create it, and referencing a collection does not create it either.
The database and the collection first appear on disk when the first document is written to them.
// https://www.mongodb.com/docs/manual/core/databases-and-collections/
use shop // no-op until something is written
db.products.insertOne({ _id: 1, name: "Widget", price: 9.99 })
// -> database "shop" and collection "products" now exist
db.getMongo().getDBNames() // "shop" is listed only after the insert
show collections
An index build (createIndex) on a missing collection also creates it. To create a collection with no
documents and no options, call createCollection with just a name.
Naming rules and restrictions
Database names are case-sensitive, limited to 63 bytes, and cannot contain /\. "$*<>:|? or the null
character; admin, local and config are reserved for the system. Collection names cannot start with
system., cannot contain $ or the null character, cannot be empty, and the fully-qualified
<db>.<collection> namespace has a length limit. See
https://www.mongodb.com/docs/manual/reference/limits/#naming-restrictions for the exact list.
db.createCollection("audit_log") // explicit, empty, default options
db.createCollection("orders", { collation: { locale: "en", strength: 2 } })
// inspect sizes and counts
db.stats() // storage totals for the whole database
db.orders.stats() // per-collection storage, index sizes, counts
// https://www.mongodb.com/docs/manual/reference/method/db.stats/
Flexible schema is not "no schema"
MongoDB does not require a collection-wide schema declaration: two documents in one collection may carry different fields and different BSON types for the same field name. That flexibility is a migration and modelling convenience, not an absence of structure — application code, indexes and queries all still assume a shape. A field’s meaning, type and presence are part of the design; they are simply enforced by the application, by optional validation rules, or both, rather than by a mandatory DDL step. See https://www.mongodb.com/docs/manual/core/data-modeling-introduction/ and Data modeling.
// both documents are valid in the same collection, but the second is probably a bug
{ "_id": 1, "sku": "A-1", "price": 9.99, "tags": ["new"] }
{ "_id": 2, "sku": 42, "price": "cheap" }
Schema validation with $jsonSchema
db.createCollection (or collMod for an existing collection) accepts a validator. The $jsonSchema
operator is the recommended form: it constrains required fields, BSON types, value ranges and enumerations.
validationLevel selects which documents are checked and validationAction selects what happens on failure.
-
validationLevel:strict(default — check every insert and every update) ormoderate(check inserts, and updates only to documents that already satisfy the schema, leaving existing invalid documents alone). -
validationAction:error(default — reject the write) orwarn(allow the write, record a warning in the log).
// https://www.mongodb.com/docs/manual/core/schema-validation/
db.createCollection("customers", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["email", "createdAt"],
properties: {
email: { bsonType: "string", pattern: "^.+@.+$" },
createdAt: { bsonType: "date" },
tier: { enum: ["free", "pro", "enterprise"] }
}
}
},
validationLevel: "moderate",
validationAction: "warn"
})
// tighten the rules later without recreating the collection
db.runCommand({
collMod: "customers",
validator: { $jsonSchema: { bsonType: "object", required: ["email", "createdAt", "tier"] } },
validationLevel: "strict",
validationAction: "error"
})
A write that violates a strict / error validator fails:
{
"ok": 0,
"code": 121,
"codeName": "DocumentValidationFailure",
"errmsg": "Document failed validation"
}
Capped collections
A capped collection is created with a fixed maximum size in bytes (and optionally a maximum document count).
It preserves insertion order and, once full, overwrites its oldest documents to make room for new ones.
Documents cannot be deleted individually (deleteOne / deleteMany are disallowed) and an update must not
grow a document. Capped collections support tailable cursors, which stay open after reaching the end and
return new documents as they are inserted — useful for feed-style consumers.
// https://www.mongodb.com/docs/manual/core/capped-collections/
db.createCollection("recent_events", { capped: true, size: 1048576, max: 5000 })
db.recent_events.insertOne({ at: new Date(), kind: "login", user: "ada" })
mongosh has no tailable-cursor helper; the drivers do. With the Node.js driver you pass the cursor options
and keep iterating — when the cursor reaches the end it blocks (up to maxAwaitTimeMS) instead of closing:
// Node.js driver: keep reading recent_events as new documents arrive
const cursor = db.collection("recent_events")
.find({}, { tailable: true, awaitData: true, maxAwaitTimeMS: 1000 });
for await (const event of cursor) {
handle(event);
}
For most "keep only recent data" needs a TTL collection (below) is a better fit, because it allows normal deletes and updates.
TTL collections
A TTL ("time to live") collection is an ordinary collection with a single-field TTL index on a Date field.
A background thread deletes documents once the indexed date is older than expireAfterSeconds. Set
expireAfterSeconds: 0 to expire exactly at the stored date.
// https://www.mongodb.com/docs/manual/core/index-ttl/
db.sessions.createIndex({ lastActive: 1 }, { expireAfterSeconds: 3600 })
db.sessions.insertOne({ _id: "sess-1", lastActive: new Date() })
// removed by the TTL monitor roughly one hour after lastActive
See Indexes for TTL index constraints (single field, not on _id, not
compound).
Clustered collections
A clustered collection stores its documents in _id index order — the collection and its primary index
are the same B-tree, so there is no separate _id index and _id-based lookups and range scans avoid a
second seek. It is created with clusteredIndex and the key must be { _id: 1 }.
// https://www.mongodb.com/docs/manual/core/clustered-collections/
db.createCollection("readings", {
clusteredIndex: { key: { _id: 1 }, unique: true, name: "readings_id_clustered" }
})
Best suited to collections queried mostly by _id or by _id ranges, or that would otherwise carry a large
_id index.
Views
A view is a read-only, non-materialised query object defined by a name, a source collection, and an aggregation pipeline. Reading the view runs the pipeline against the source each time; the view stores no data of its own and cannot be written to.
// https://www.mongodb.com/docs/manual/core/views/
db.createView(
"active_customers", // view name
"customers", // source collection
[
{ $match: { tier: { $ne: "free" } } },
{ $project: { email: 1, tier: 1, _id: 0 } }
]
)
db.active_customers.find({ tier: "pro" }) // pipeline runs now, against customers
On-demand materialized views
When the pipeline is expensive and staleness is acceptable, write its result to a real collection with a
$merge (or $out) stage and re-run it on a schedule. The target collection can be indexed and queried like
any other; $merge updates it in place rather than replacing it wholesale.
// https://www.mongodb.com/docs/manual/core/materialized-views/
db.orders.aggregate([
{ $group: { _id: "$customerId", spend: { $sum: "$total" }, orders: { $sum: 1 } } },
{ $merge: { into: "customer_spend", whenMatched: "replace", whenNotMatched: "insert" } }
])
See Aggregation pipeline for the full $merge stage
options.
Time series collections
For a stream of timestamped measurements (metrics, sensor readings, prices), a time series collection
stores data in a compressed, time-bucketed layout and is created with a timeseries option naming the time
field and an optional metadata field. It is the purpose-built alternative to hand-rolling a capped or TTL
collection for this data.
// overview only -- see https://www.mongodb.com/docs/manual/core/timeseries-collections/
db.createCollection("weather", {
timeseries: { timeField: "ts", metaField: "station", granularity: "minutes" },
expireAfterSeconds: 7776000
})