Day 4 – MongoDB: Document Databases
Today's Focus
Learn what a document database is, understand when schema flexibility is valuable, and connect to MongoDB from all four languages to perform CRUD operations.
What is a Document Database?
In MongoDB, data is stored as BSON documents (Binary JSON) in collections. There is no fixed schema — different documents in the same collection can have different fields.
This is useful when:
- The data structure varies between records (e.g. products with different attributes)
- You need to store nested objects or arrays naturally without joins
- The schema evolves rapidly during development
- You are storing event logs, user activity, or content that does not fit neatly into rows and columns
Relational vs Document: The Same Data, Two Models
The same "language with frameworks" data looks very different in each model.
Relational — two tables, requires a JOIN:
SELECT l.name, f.name
FROM languages l
JOIN frameworks f ON f.language_id = l.id
Document — one document per language with an embedded array:
{
"name": "Python",
"typing": "dynamic",
"frameworks": ["FastAPI", "Django", "Flask"]
}
The document approach avoids the JOIN and makes reads simpler when you always want the full language with its frameworks. The trade-off: if you need to query frameworks independently (e.g. "which language uses Django?"), the document model requires scanning all documents or building an index on the array field.
Neither model is universally better. The right choice depends on how the data is accessed.
Installing MongoDB
macOS:
brew tap mongodb/brew
brew install mongodb-community
brew services start mongodb-community
Linux: follow the official apt repository instructions at docs.mongodb.com.
Verify:
mongosh --version
mongosh
MongoDB Shell Basics
use academy
db.languages.insertOne({ name: "Python", typing: "dynamic", frameworks: ["FastAPI", "Django"] })
db.languages.find()
db.languages.find({ typing: "static" })
db.languages.updateOne(
{ name: "Python" },
{ $push: { frameworks: "Flask" } }
)
db.languages.deleteOne({ name: "Python" })
MongoDB in All Four Languages
Python
from pymongo import MongoClient
client = MongoClient("mongodb://localhost:27017")
db = client["academy"]
collection = db["languages"]
collection.drop() # start fresh each run
collection.insert_many([
{"name": "Python", "typing": "dynamic", "paradigm": "multi-paradigm", "frameworks": ["FastAPI", "Django"]},
{"name": "JavaScript", "typing": "dynamic", "paradigm": "multi-paradigm", "frameworks": ["Express", "Next.js"]},
{"name": "C#", "typing": "static", "paradigm": "object-oriented", "frameworks": ["ASP.NET Core"]},
{"name": "Go", "typing": "static", "paradigm": "procedural", "frameworks": ["Gin", "Echo"]},
])
for doc in collection.find({"typing": "static"}):
print(doc["name"], doc.get("frameworks", []))
collection.update_one({"name": "Go"}, {"$push": {"frameworks": "Fiber"}})
print(collection.find_one({"name": "Go"}))
Setup: uv add pymongo
Node.js
const { MongoClient } = require('mongodb')
async function main() {
const client = new MongoClient('mongodb://localhost:27017')
await client.connect()
const db = client.db('academy')
const coll = db.collection('languages')
await coll.drop().catch(() => {}) // ignore error if collection does not exist
await coll.insertMany([
{ name: 'Python', typing: 'dynamic', frameworks: ['FastAPI', 'Django'] },
{ name: 'JavaScript', typing: 'dynamic', frameworks: ['Express', 'Next.js'] },
{ name: 'C#', typing: 'static', frameworks: ['ASP.NET Core'] },
{ name: 'Go', typing: 'static', frameworks: ['Gin', 'Echo'] },
])
const staticLangs = await coll.find({ typing: 'static' }).toArray()
console.log(staticLangs.map(l => l.name))
await coll.updateOne({ name: 'Go' }, { $push: { frameworks: 'Fiber' } })
await client.close()
}
main().catch(console.error)
Setup: npm install mongodb
C#
using MongoDB.Driver;
using MongoDB.Bson;
var client = new MongoClient("mongodb://localhost:27017");
var db = client.GetDatabase("academy");
var coll = db.GetCollection<BsonDocument>("languages");
await coll.DeleteManyAsync(new BsonDocument());
await coll.InsertManyAsync(new[]
{
new BsonDocument
{
["name"] = "Python",
["typing"] = "dynamic",
["frameworks"] = new BsonArray { "FastAPI", "Django" },
},
new BsonDocument
{
["name"] = "JavaScript",
["typing"] = "dynamic",
["frameworks"] = new BsonArray { "Express" },
},
new BsonDocument
{
["name"] = "C#",
["typing"] = "static",
["frameworks"] = new BsonArray { "ASP.NET Core" },
},
new BsonDocument
{
["name"] = "Go",
["typing"] = "static",
["frameworks"] = new BsonArray { "Gin", "Echo" },
},
});
var filter = Builders<BsonDocument>.Filter.Eq("typing", "static");
var docs = await coll.Find(filter).ToListAsync();
foreach (var doc in docs)
Console.WriteLine($"{doc["name"]}: {doc["frameworks"]}");
Setup: dotnet add package MongoDB.Driver
Go
package main
import (
"context"
"fmt"
"log"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func main() {
ctx := context.Background()
client, err := mongo.Connect(ctx, options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
log.Fatal(err)
}
defer client.Disconnect(ctx)
coll := client.Database("academy").Collection("languages")
coll.Drop(ctx)
docs := []interface{}{
bson.D{{"name", "Python"}, {"typing", "dynamic"}, {"frameworks", bson.A{"FastAPI", "Django"}}},
bson.D{{"name", "JavaScript"}, {"typing", "dynamic"}, {"frameworks", bson.A{"Express"}}},
bson.D{{"name", "C#"}, {"typing", "static"}, {"frameworks", bson.A{"ASP.NET Core"}}},
bson.D{{"name", "Go"}, {"typing", "static"}, {"frameworks", bson.A{"Gin", "Echo"}}},
}
coll.InsertMany(ctx, docs)
cursor, err := coll.Find(ctx, bson.D{{"typing", "static"}})
if err != nil {
log.Fatal(err)
}
defer cursor.Close(ctx)
for cursor.Next(ctx) {
var result bson.M
cursor.Decode(&result)
fmt.Println(result["name"], result["frameworks"])
}
}
Setup: go get go.mongodb.org/mongo-driver/mongo
Tasks
- Install MongoDB and verify it is running:
mongosh
db.runCommand({ ping: 1 })
- Run all four language examples. After each one, open
mongosh, switch to theacademydatabase, and inspect the documents:
use academy
db.languages.find().pretty()
- Add a
year_createdfield to the Python document only, using an update from whichever language you prefer:
collection.update_one({"name": "Python"}, {"$set": {"year_created": 1991}})
Then run db.languages.find() in mongosh and observe that only the Python document has year_created. The other documents are unaffected. This is schema flexibility in action.
-
Research the correct MongoDB query operator to find documents whose
frameworksarray contains more than one entry. The$sizeoperator matches arrays of an exact length, but for "greater than", you need a different approach — look up$whereand$exprin the MongoDB documentation, or use$existswith$gton an index. Write the query that works. -
Discuss: when would you choose MongoDB over PostgreSQL for the
languagesdata? When would PostgreSQL be the better fit? Consider: what queries do you need? How often does the schema change? Do you need transactions across multiple documents?