MongoDB stores JSON-like documents instead of rows. That flexibility is useful when records genuinely differ, and dangerous when it becomes an excuse to skip schema design.
Learning objectives
- Explain collections, documents and ObjectIds.
- Model data with Mongoose schemas.
- Connect to a local database or MongoDB Atlas.
- Build CRUD routes with validation.
- Add indexes and filter on array fields.
1. Documents and collections
database: aivasu
└── collection: memos
├── { _id: ObjectId("..."), title: "First", tags: ["node"], createdAt: ... }
└── { _id: ObjectId("..."), title: "Second", tags: [], createdAt: ... }
Documents can have different fields, and nested objects and arrays are stored directly. Relations exist as references (authorId) or by embedding. Embed when the child is always read with the parent and never grows without bound; reference when it is shared or large.
2. Setup
Local: install MongoDB Community and run mongod. The default URL is mongodb://127.0.0.1:27017/aivasu.
Atlas: create a free cluster, add your IP, create a database user, and copy the connection string into .env:
MONGODB_URI=mongodb+srv://user:[email protected]/aivasu
npm install mongoose
require("dotenv").config();
const mongoose = require("mongoose");
async function connect() {
await mongoose.connect(process.env.MONGODB_URI);
console.log("MongoDB connected");
}
connect().catch((error) => {
console.error(error.message);
process.exit(1);
});
Never commit the Atlas password. Whitelist only the IPs that need access.
3. A Mongoose model
const mongoose = require("mongoose");
const memoSchema = new mongoose.Schema(
{
title: {
type: String,
required: [true, "Title is required"],
trim: true,
maxlength: [100, "Title must be 100 characters or fewer"],
},
content: { type: String, required: true, trim: true },
tags: { type: [String], default: [] },
isPublic: { type: Boolean, default: false },
},
{ timestamps: true },
);
memoSchema.index({ tags: 1 });
memoSchema.index({ createdAt: -1 });
module.exports = mongoose.model("Memo", memoSchema);
mongoose.model("Memo", ...) creates a memos collection. timestamps: true maintains createdAt and updatedAt.
4. CRUD
const Memo = require("./models/Memo");
// create — runs schema validation
const memo = await Memo.create({ title: "First", content: "Hello", tags: ["intro"] });
// list with filter, sort, pagination
const page = Math.max(1, Number(req.query.page) || 1);
const limit = Math.min(50, Number(req.query.limit) || 10);
const filter = {};
if (req.query.tag) filter.tags = req.query.tag; // matches an array element
if (req.query.q) filter.title = { $regex: req.query.q, $options: "i" };
const [items, total] = await Promise.all([
Memo.find(filter).sort({ createdAt: -1 }).skip((page - 1) * limit).limit(limit),
Memo.countDocuments(filter),
]);
// read one
const one = await Memo.findById(req.params.id);
// update
const updated = await Memo.findByIdAndUpdate(
req.params.id,
{ $set: { title: "Updated" } },
{ new: true, runValidators: true },
);
// delete
await Memo.findByIdAndDelete(req.params.id);
{ new: true } returns the updated document instead of the old one. runValidators: true is required — update queries skip schema validation by default.
5. Errors
try {
await Memo.create({ content: "No title" });
} catch (error) {
if (error.name === "ValidationError") {
return res.status(400).json({ error: Object.values(error.errors).map((e) => e.message) });
}
if (error.name === "CastError") {
return res.status(400).json({ error: "Invalid id" });
}
throw error;
}
An invalid ObjectId throws CastError; guard it before returning a 500.
6. Choosing between embedded and referenced data
- Embed: comments on a post if they are always shown together and bounded in number.
- Reference: the post's author, because users exist independently and are shared across posts.
- Neither: never embed an ever-growing array into a document; MongoDB documents have a 16 MB limit and large arrays are slow to update.
Common mistakes
- Treating Mongoose as optional validation —
findOneAndUpdatebypasses validators unless told otherwise. - Storing the connection string in code.
- Returning the whole document including internal fields to the client.
- Assuming MongoDB is "schemaless" and skipping indexes; unindexed queries scan every document.
Practice
Build the memo API in the assignment with tags, filtering and pagination.