← Back to Index

Databases &
Collections

FILE 02_databases_collections
TOPIC Hierarchy · Naming Rules · Commands · Lazy Creation
LEVEL Foundation
01
Hierarchy
The containment model from server to field
Core

MongoDB organizes data in a strict top-down hierarchy. Understanding this model determines how you design your schema, run queries, and scope operations like drops and backups.

Containment Model

Server
 └── Database            (logical grouping of collections)
      └── Collection     (grouping of related documents)
           └── Document  (a single BSON record)
                └── Fields  (key-value pairs within the document)

A single MongoDB server (or replica set / sharded cluster) can host many databases. Each database holds many collections, each collection holds many documents, and each document holds many fields.

Reserved Databases

MongoDB ships with three built-in databases you should never accidentally drop or repurpose:

Name Purpose Notes
admin Authentication, authorization, and server-wide commands Root user lives here; db.adminCommand() always targets this DB
local Stores the oplog and replication state Never replicated to secondaries — each node maintains its own copy
config Sharding metadata — chunk ranges, shard mappings, balancer state Managed by mongos; do not write to it manually

Lazy Creation — The Core Concept

Unlike relational databases, MongoDB does not physically create a database or collection until you write actual data into it. Switching to a database with use myDB is purely a context change in the shell — no disk allocation, no namespace reservation, nothing persists until the first write.

TIP
The lazy creation model means your schema evolves naturally. You never need DDL statements like CREATE DATABASE or CREATE TABLE before inserting. Just insert and the structure materializes automatically on disk.
02
Databases
Naming rules, commands, and the lazy creation gotcha
Commands

Naming Rules

Database names are case-insensitive on most systems and have a maximum of 64 characters. The following characters are forbidden:

PlatformForbidden Characters
Windows / \ . " (space) $ * : | ?
Unix / Linux / macOS / \ . " (space) $
WARN
Use only lowercase letters, digits, and underscores for maximum portability. A name that works on macOS may fail or produce unexpected behavior when deployed to a Linux production server.

Essential Database Commands

// Switch to (or set context for) a database
use myDB

// Show the name of the current active database
db

// List all non-empty databases on the server
show dbs

// Drop the CURRENT database (whichever db is set to)
db.dropDatabase()

Lazy Creation Gotcha

Running use newDB sets your shell context but creates absolutely nothing on disk. The database will not appear in show dbs until at least one document has been written into it.

// Switch context — nothing created yet
use newDB

// Still won't appear — no data has been written
show dbs

// First write causes the DB to materialize on disk
db.users.insertOne({ name: "Alice" })

// Now newDB is visible
show dbs

Safe Drop Workflow

Always confirm which database is active before calling dropDatabase(). There is no confirmation prompt and no undo. Follow this three-step pattern every time:

// Step 1: Confirm which DB you are on
db
// Output: myDB

// Step 2: Review what's inside
show collections
// Output: users, orders, products

// Step 3: Drop only after confirming
db.dropDatabase()
// Output: { ok: 1, dropped: 'myDB' }
DANGER
Never drop the admin or local databases. Dropping admin removes all users and roles server-wide. Dropping local corrupts replication state. These operations are immediate and permanent — there is no undo.
03
Collections
Schema-less groupings · naming rules · capped collections
Schema

Schema-less Nature

Collections are the MongoDB equivalent of SQL tables, but with a fundamental difference: there is no enforced schema by default. Documents inside the same collection can have entirely different fields, different data types, and different nesting depths. This flexibility is intentional and enables rapid iteration without migrations.

NOTE
Schema-less does not mean schema-free in practice. Use JSON Schema validation via the $jsonSchema validator on createCollection() to enforce structure at the database level when consistency is required.

Naming Rules

  • Must start with an underscore (_) or a letter (a–z, A–Z)
  • Cannot begin with system. — that prefix is reserved for MongoDB internal collections
  • Cannot contain the $ character or the null character (\0)
  • Maximum 120 bytes total including the database name prefix (e.g., myDB.collectionName counts together)

Essential Collection Commands

// List all collections in the current database
show collections

// Explicitly create a collection (options are optional)
db.createCollection("products")

// Drop a specific collection and all its documents
db.products.drop()

// Implicit creation — collection is created on first insert
db.newCollection.insertOne({ x: 1 })

Explicit vs Implicit Creation

// Explicit: required when you need options like a validator or collation
db.createCollection("orders", {
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["customerId", "total"],
      properties: {
        total: { bsonType: "number", minimum: 0 }
      }
    }
  }
})

// Implicit: MongoDB creates the collection automatically on insert
db.orders.insertOne({ customerId: "c1", total: 99.99 })
// Collection 'orders' is created if it did not already exist

Capped Collections

A capped collection has a fixed maximum size in bytes. Once full, it automatically overwrites the oldest documents in insertion order — like a circular buffer. Documents are always returned in natural insertion order.

// Create a capped collection: max 1MB storage, max 1000 documents
db.createCollection("auditLog", {
  capped: true,
  size: 1048576,   // bytes — REQUIRED field for capped collections
  max: 1000        // document count limit — OPTIONAL
})
TIP
Use capped collections for rolling logs, audit trails, and event streams where you only care about the most recent N records. They provide automatic size management with no separate cleanup job required.
04
Documents
16MB limit · _id rules · field names · dot notation
BSON

Size Limit

A single BSON document has a hard maximum size of 16 MB. This is enforced at both the driver and server level — inserting a larger document throws a BSONObjectTooLarge error. For files, binary blobs, or any data exceeding 16 MB, use GridFS, which splits data into 255 KB chunks stored across two collections: fs.files (metadata) and fs.chunks (binary data).

The _id Field

Every document must have an _id field. It is the primary key of the collection and the basis for the default index.

PropertyDetail
Mandatory Every document must have one — MongoDB auto-generates it if omitted
Immutable Cannot be updated after insertion; replace the whole document to change it
Unique Duplicate _id values in the same collection throw a duplicate key error
Auto-generated Default type is ObjectId — 12 bytes encoding timestamp + machine ID + PID + counter
Cannot be an array Arrays are explicitly disallowed as _id values
Always first Field ordering is preserved; _id is always stored first regardless of insertion order

Field Names

  • Must be UTF-8 encoded strings
  • Top-level field names cannot start with $ — reserved for query operators
  • Avoid dots (.) in field names — they conflict with dot notation path access
  • The null character (\0) is not permitted

Dot Notation for Nested Access

MongoDB uses dot notation to address embedded documents and array elements in queries, updates, and projections without pulling the entire document into application memory.

// Inserting a document with nested structure
db.users.insertOne({
  name: "Alice",
  age: 29,
  address: {
    city: "London",
    postcode: "EC1A 1BB",
    geo: { lat: 51.5074, lng: -0.1278 }
  },
  tags: ["mongodb", "developer"]
})

// Query on a nested field using dot notation
db.users.find({ "address.city": "London" })

// Deep nesting — any depth is supported
db.users.find({ "address.geo.lat": { $gt: 51 } })

// Access a specific array element by index
db.users.find({ "tags.0": "mongodb" })

// Projection: return only the city sub-field, suppress _id
db.users.find({}, { "address.city": 1, _id: 0 })
WARN
Field order within a BSON document is preserved on read but is not guaranteed to be stable across replacement updates. Never write application logic that depends on relative field position inside a document.
05
Shell Commands
Complete reference for mongosh database and collection operations
Reference

Database Commands

CommandPurpose
use <name> Switch to database context; creates lazily on first write
show dbs List all non-empty databases visible on the server
db Print the name of the current active database
db.dropDatabase() Drop the current database and all its collections permanently

Collection Commands

CommandPurpose
show collections List all collections in the current database
db.createCollection("name") Explicitly create a collection; accepts an optional options object
db.col.drop() Drop the named collection and all documents it contains

Document Stats Commands

CommandPurpose
db.col.countDocuments() Accurate document count via full collection scan; accepts a filter query
db.col.estimatedDocumentCount() Fast estimate from collection metadata; no filter support; best for dashboards
db.stats() Database-level storage stats: total size on disk, collection count, index count
db.col.stats() Collection-level stats: document count, average object size, index sizes

db.stats() Example Output

{
  db: 'myDB',
  collections: 3,
  views: 0,
  objects: 15280,          // total documents across all collections
  avgObjSize: 312,         // average bytes per document
  dataSize: 4767360,       // logical size of all documents in bytes
  storageSize: 2883584,    // actual allocated disk space (compressed)
  indexes: 5,
  indexSize: 1093632,
  totalSize: 3977216,      // storageSize + indexSize combined
  ok: 1
}
NOTE
storageSize is typically smaller than dataSize because WiredTiger applies Snappy compression by default. Text-heavy collections often achieve 2–4x compression ratios. You can configure zstd for even better compression on large datasets.
06
Edge Cases
Gotchas, traps, and common mistakes to avoid
Gotchas

Lazy Creation: use Does Not Create

Switching to a database with use newDB is purely a shell context change. No namespace is reserved, no files are created, and the database will not appear in show dbs until actual data is written.

// This does NOT create a database — just sets the context
use phantomDB

// phantomDB is absent from this list
show dbs

// The DB materializes only after the first write
db.test.insertOne({ created: true })
show dbs  // phantomDB now appears

Case-Insensitive Names

On macOS (HFS+) and Windows (NTFS), database names are case-insensitive. MyDB and mydb refer to the same database. On Linux with ext4, they are different databases — a common source of environment-specific bugs when developing locally and deploying to Linux servers.

// On macOS / Windows — these refer to the SAME database
use MyDB
use mydb   // same underlying namespace on case-insensitive FS

// Always use lowercase to stay fully portable
use my_db  // safe on all platforms

show dbs Hides Empty Databases

Databases that contain no collections with documents are not shown in show dbs. This is intentional, not a bug. A newly switched-to database remains invisible until written to. If you expect to see a database but it is missing from the list, confirm that at least one document exists inside it.

dropDatabase Operates on the CURRENT Database

db.dropDatabase() drops whatever db is currently set to. There is no confirmation prompt and no undo. Always verify the active database before calling it.

// ALWAYS verify before dropping
db                   // confirm output: myDB
show collections     // review contents: users, orders
db.dropDatabase()    // only proceed after confirming

// Classic mistake: context was switched earlier and forgotten
use wrongDB          // done earlier in the session
db.dropDatabase()    // !!! dropped wrongDB — not intended

Reserved Collection Prefix: system.

Any collection name beginning with system. is reserved for MongoDB internal use. Attempting to create such a collection will either throw an error or silently corrupt internal metadata.

// This will throw: Invalid collection name
db.createCollection("system.users")

// Safe alternatives — avoid the system. prefix entirely
db.createCollection("app_users")   // ok
db.createCollection("_users")      // ok
DANGER
Never write directly to system.users or system.roles. Always use the official commands: db.createUser(), db.updateUser(), db.grantRolesToUser(). Bypassing these commands can produce an inconsistent auth state that is difficult to recover from.