If you have ever used a SQL database like MySQL or PostgreSQL, MongoDB will both feel familiar and throw you off balance. The mental model is similar (you store data, you query it, you update it) but the shape of the data is completely different. MongoDB is a document database: instead of rows and tables, you store JSON-like objects. This makes it a joy for some workloads and a pain for others. This article helps you figure out when it is the right choice, and how to use it well when it is.

What is a document database?

In a SQL database, your data lives in tables with a fixed schema. Each row is a list of column values. Cross-table relationships are explicit foreign keys. In a document database, your data lives in flexible documents — JSON-like structures with fields that can vary from document to document. There are no fixed schemas, no joins, no rigid column types.

That flexibility has trade-offs. You give up the safety net of a strict schema, and you lose easy cross-document joins. In return, you get a much closer fit to the shape of your application data — most modern apps are built around objects, and storing them as objects is dramatically simpler.

The basic shape of a document

A document is a JSON object with string keys and any JSON value. Here is a typical blog post document:

{
  "_id": "65f1c8a2e4b09a8f7c1d2e3f",
  "title": "Why we picked Vue",
  "slug": "why-we-picked-vue",
  "author": { "name": "Mango", "email": "team@mangobaz.com" },
  "tags": ["vue", "framework", "opinion"],
  "views": 1432,
  "publishedAt": "2026-04-12T10:00:00Z",
  "draft": false
}

Notice the nesting: author is a sub-document, tags is an array. That is allowed in MongoDB. In SQL, you would have to make a separate authors table and a join table for tags. Here, the relationships are embedded. That is the fundamental choice you make when modelling for MongoDB.

Databases, collections, documents

The hierarchy mirrors SQL, but the names differ:

  • Database — like a SQL database. Top-level container.
  • Collection — like a SQL table. Holds documents of a similar type.
  • Document — like a SQL row. A single JSON-like object.

There is no schema enforcement by default. You can have one document with title and another in the same collection without it. You can add validation rules with JSON Schema if you want safety, but most teams skip that.

Connecting and inserting

MongoDB ships with a JavaScript shell (mongosh) for interactive use. For code, every major language has an official driver. Here is the Node.js example:

import { MongoClient } from "mongodb";

const client = new MongoClient(process.env.MONGO_URL);
await client.connect();
const db = client.db("blog");
const articles = db.collection("articles");

await articles.insertOne({
  title: "Hello, MongoDB",
  author: "Mango",
  publishedAt: new Date(),
});

The same idea works in Python, C#, Go, and every other major language. The driver handles connection pooling, retries, and serialisation for you.

Querying with find

find is the workhorse. Pass it a filter object and it returns matching documents:

const drafts = await articles
  .find({ draft: true })
  .sort({ publishedAt: -1 })
  .limit(10)
  .toArray();

The filter object uses MongoDB's query operators. { views: { $gt: 1000 } } means "more than 1000 views." { tags: "vue" } matches any document whose tags array contains "vue". { $or: [{ draft: false }, { views: { $gt: 100 } }] } is the logical OR. Most filters are simple equality, but you can compose arbitrarily.

Updating documents

updateOne and updateMany change existing documents. They take a filter and an update document:

await articles.updateOne(
  { _id: articleId },
  { $set: { views: 1500 }, $inc: { views: 1 } }
);

The update operators are powerful: $set changes fields, $inc increments numbers, $push adds to arrays, $pull removes from arrays, $unset deletes a field. Avoid replacing the whole document with replaceOne unless you mean it — partial updates are clearer.

Indexes: the secret to performance

MongoDB collections are fast for small data sets and slow for large ones, unless you add indexes. An index is a data structure that lets MongoDB find documents by a field without scanning every one. The MongoDB index documentation is the canonical reference.

await articles.createIndex({ publishedAt: -1 });
await articles.createIndex({ tags: 1 });
await articles.createIndex({ title: "text" });

The first index sorts by publication date. The second makes tag-based queries fast. The third is a text index, which lets you run full-text search with $text. You can also create compound indexes for queries that filter and sort together. Indexes cost write performance and disk space, so add them thoughtfully.

Embed vs reference: the modelling decision

The single biggest modelling choice in MongoDB is whether to embed related data in a document or reference it from another. The general rule:

  • Embed when the related data is always loaded with the parent (an article's tags, a user's profile photo, a comment's author name).
  • Reference when the related data can be huge (a book's chapters), is shared (a tag used by 10000 articles), or is loaded separately.

Embedding gives you one read for the whole object. Referencing gives you flexibility but requires extra queries or joins. For a typical blog, articles embed their tags and reference their author. For a social network, posts embed comments up to a threshold and reference them when the thread gets long.

Aggregation pipelines: the power tool

When find is not enough, MongoDB has an aggregation pipeline — a multi-stage transformation:

const stats = await articles.aggregate([
  { $match: { draft: false } },
  { $group: { _id: "$author.name", count: { $sum: 1 } } },
  { $sort: { count: -1 } },
  { $limit: 10 },
]).toArray();

That pipeline finds all published articles, groups them by author name, counts the documents per group, sorts by count descending, and takes the top ten. Aggregations can do anything SQL can do, and quite a bit more (especially with the geospatial and array operators).

When to skip MongoDB

MongoDB is excellent for many things but not everything. Skip it when:

  • You need strict ACID transactions across many records (use Postgres or MySQL).
  • Your data is highly relational and you need many joins (use Postgres or MySQL).
  • You have a small dataset that fits in memory (any embedded DB like SQLite is faster and simpler).
  • You need a fixed schema enforced by the database (use Postgres with strict types).

For most web apps with modest relational complexity, MongoDB is a perfectly fine default. For finance, compliance, or anything where data integrity is paramount, stick with a SQL database.

Common pitfalls

  • Not indexing hot queries. Run db.collection.explain("executionStats") on slow queries and add the indexes they need.
  • Unbounded arrays. Never let an array grow without limit. If it might exceed a few hundred items, use a separate collection.
  • Embedding when you should reference. If the embedded data has a one-to-many or many-to-many relationship with the parent, references are usually better.
  • Using string IDs without thinking. MongoDB's default _id is an ObjectId. If you want a custom format (a slug, a UUID), declare it explicitly and validate it.
  • Forgetting about backups. Use MongoDB Atlas (managed) or set up regular mongodump exports. An unwiped production database is heartbreaking to lose.

Replication and sharding: when you outgrow one machine

For most apps, a single MongoDB server is plenty. When you outgrow it, MongoDB gives you two paths. Replication copies your data across multiple servers (a "replica set") so that one can fail and the others take over. Sharding splits your data across multiple servers so each one holds a subset. Most production deployments use both: a sharded cluster, where each shard is a replica set. Atlas handles all of this for you with a single toggle.

You usually do not need to think about replication or sharding until your data is in the hundreds of gigabytes. For most of the apps you will build, a single-node deployment is fine.

Change streams: real-time updates

MongoDB supports change streams, a feature that lets you subscribe to changes on a collection. Every insert, update, or delete emits an event you can react to:

const stream = articles.watch();
stream.on("change", (change) => {
  console.log("Article changed:", change);
});

This is how you build real-time features (live notifications, real-time dashboards) without polling. Change streams require a replica set, which Atlas sets up by default.

Further reading

MongoDB has excellent first-party documentation. Start here.

FAQ

Is MongoDB faster than SQL?

It depends. For simple document-shaped reads with no joins, MongoDB is usually faster. For complex joins across many tables, SQL wins. For write-heavy workloads, they are roughly comparable.

Should I use MongoDB Atlas or self-host?

Atlas is the easiest path. Free tier covers small projects. Self-hosting makes sense if you have specific compliance requirements or a very large dataset.

How do I migrate from SQL to MongoDB?

Slowly. Start by writing new features against MongoDB. Migrate old data with a script that reads from SQL and writes to Mongo. Keep both running during the transition. Do not rewrite everything at once.

What is BSON?

Binary JSON. MongoDB stores documents in BSON, a binary format that supports more types than JSON (dates, ObjectIds, binary data). The wire protocol is BSON too. You almost never need to think about it directly.

Can I use transactions?

Yes, since MongoDB 4.0 for replica sets. Multi-document transactions work but are slower than single-document updates. Use them sparingly — most well-modelled MongoDB apps do not need them.

What is the difference between MongoDB and a key-value store like Redis?

Redis stores opaque values by key. MongoDB stores structured documents you can query by any field. Use Redis for caching and ephemeral state; use MongoDB for durable data you want to query.

Take your time, work through each step, and read errors carefully — they tell you what went wrong.

Homework

Build a small articles store on MongoDB. Spin up a free-tier Atlas cluster, install the Node driver, and write a small script that:

  • Inserts 10 sample articles with varied tags, authors, and view counts.
  • Creates an index on the publishedAt field.
  • Queries and prints the top 5 articles by view count.
  • Uses an aggregation pipeline to count articles per author.
  • Updates one article to add a new tag.

Run the script, check the output, and read the MongoDB Atlas query profiler to see how your indexes are being used. If you get stuck, the MongoDB manual is excellent and searchable.