WEEK 12 · ISLAND OF MEMORY

Databases with Express

Last week your anime lived in a JSON file โ€” wiped on every restart. This week they move into a real database with MongoDB + Mongoose, and finally remember. Run real queries in the emulator below. ๐Ÿ—„๏ธ

This week NoSQL MongoDB documents Mongoose (ODM) Schema & Model CRUD

0 Week at a glance

array in memory โ†’ real database. Your data survives restarts, scales, and lives on its own.
  • DBMS โ€” software that stores, retrieves and updates data (MongoDB, MySQL, PostgreSQLโ€ฆ).
  • MongoDB โ€” a NoSQL, document database: your data looks like JSON objects, not rigid tables.
  • Mongoose โ€” the ODM that lets Node talk to MongoDB with tidy Schemas and Models.
  • CRUD โ€” create ยท find ยท updateOne ยท deleteOne โ€” the four things every app does to data.

New this week: the Mongoose emulator. Every ๐Ÿƒ mongoose emulator block runs a tiny MongoDB in your browser โ€” real find, create, updateOne, deleteMany with operators like $gte and $regex. It comes pre-loaded with 5 anime, and each Run resets to those 5, so examples are independent.

1 Databases & the DBMS

Key terms database DBMS SQL vs NoSQL
  • A database (DB) is an organized collection of data.
  • A DBMS (Database Management System) is the software that handles storing, retrieving and updating it.
  • When people say "the database," they usually mean the data plus the DBMS managing it.
Two big families: relational (SQL โ€” tables, e.g. MySQL, PostgreSQL) and document (NoSQL โ€” e.g. MongoDB).

2 Why NoSQL?

NoSQL means no tables flexible shape looks like your code stored as JSON
Relational forces every row into the same columns (hello, empty NULLs). Documents just store what each item has.

Same two people, two ways to store them โ€” flip between the views:

nameschoolemployeroccupation
LoriNULLSelfEntrepreneur
MaliaHarvardNULLNULL

Every row must have every column, even when it doesn't apply โ€” so you get NULL filler. Add a new field later and you alter the whole table.

Why devs love it: the data looks like the objects you already use in JavaScript, it's stored as JSON, it handles big/unstructured data, and it's easy to evolve. That's the whole NoSQL pitch.

3 How MongoDB is organized

The hierarchy database collection document _id
A database holds collections; a collection holds documents. Click each layer ๐Ÿ‘‡
๐Ÿ—„๏ธ Database animeApp
๐Ÿ“š Collection animes
๐Ÿ“„ Document one anime
{
  "_id": "665f1e...c296",
  "title": "Death Note",
  "studio": "Madhouse",
  "rating": 9
}
โ˜๏ธ Click a layer to learn what it maps to in a relational database.
MongoDBRelational equivalent
DatabaseDatabase
CollectionTable
DocumentRow
FieldColumn

Two ways to poke a MongoDB directly: the mongo shell (type commands in a terminal) and Compass โ€” MongoDB's free GUI that lets you browse databases, collections and documents by clicking. Great for peeking at your data while you build.

4 Local vs cloud โ€” MongoDB Atlas

You can run MongoDB on your machine โ€” or let Atlas host it in the cloud for you.
Local MongoDBMongoDB Atlas (cloud)
Full control, works offlineManaged: backups, patching, monitoring done for you
No ongoing cost, low latency on the same machineEasy scaling with a few clicks
Better for sensitive data that can't leaveBuilt-in redundancy & global clusters

MongoDB Atlas is a fully-managed cloud database from MongoDB, Inc. โ€” host your DB in the cloud with no complex setup. Free tier: 512 MB. For this course, follow the Atlas tutorial posted in Week 12 (or the linked video) to create your account and get a connection string.

5 Meet Mongoose ๐Ÿƒ

Key terms ODM mongoose.connect connection string

Mongoose is an ODM (Object Data Modeling) library for MongoDB and Node.js. It's the friendly translator between your JavaScript objects and MongoDB documents โ€” schemas, validation, and clean CRUD methods.

install
npm install mongoose
connect returns a Promise (Week 9 pays off!) โ€” .then() on success, .catch() on failure.

Run it. Then break it: change the string to something without mongodb and watch .catch() fire.

connect.js
const mongoose = require("mongoose");

mongoose.connect("mongodb+srv://me:pass@cluster0.mongodb.net/animeApp")
  .then(() => console.log("โœ… MongoDB Connected"))
  .catch((err) => console.log("โŒ " + err.message));

Never hard-code real credentials. In a real project the connection string lives in an .env file (read with process.env), never committed to git.

6 Schemas & Models

Three steps new Schema({...}) mongoose.model() use the model
  • A Schema describes the shape of a document โ€” field names, types, and rules like required.
  • A Model is your handle to a collection โ€” you call find, create, etc. on it.
  • Mongoose auto-pluralizes the model name for the collection: model "Anime" โ†’ collection animes.
Run it โ€” define a schema, make a model, create a document, and count the collection.
model.js
const mongoose = require("mongoose");

// STEP 1 โ€” a Schema: the shape + rules of a document
const animeSchema = new mongoose.Schema({
  title:  { type: String, required: true },
  studio: String,
  rating: Number,
  year:   Number
});

// STEP 2 โ€” a Model from the schema (โ†’ collection "animes")
const Anime = mongoose.model("Anime", animeSchema);

// STEP 3 โ€” use it. create() inserts a new document.
const jjk = await Anime.create({ title: "Jujutsu Kaisen", studio: "MAPPA", rating: 9, year: 2020 });
console.log("Inserted (note the auto _id):", jjk);

console.log("The animes collection now holds:", await Anime.countDocuments());

Validation is free. Because title is required, try deleting the title from the create(...) call and Run โ€” Mongoose throws a ValidationError before anything is saved.

7 Read โ€” find & friends

Read methods find() findOne() findById() $gte ยท $lt ยท $in ยท $regex
MethodReturns
Model.find(filter)All documents that match โ€” an array (empty if none).
Model.findOne(filter)The first match โ€” or null.
Model.findById(id)The document with that _id โ€” or null.
A filter is just an object. { rating: 9 } is equality; { rating: { $gte: 9 } } uses an operator.
read.js
const mongoose = require("mongoose");
const Anime = mongoose.model("Anime", new mongoose.Schema({
  title: String, studio: String, rating: Number, year: Number
}));

// find ALL matching โ†’ array. $gte = "greater than or equal"
const great = await Anime.find({ rating: { $gte: 9 } });
console.log("Rated 9+:", great.map(a => a.title));

// findOne โ†’ the FIRST match (or null)
const one = await Anime.findOne({ studio: "Madhouse" });
console.log("A Madhouse show:", one.title);

// operators: $lt (less than) and $regex (pattern match)
const older = await Anime.find({ year: { $lt: 2010 } });
console.log("Before 2010:", older.map(a => a.title));

const man = await Anime.find({ title: { $regex: "Man", $options: "i" } });
console.log("Title has 'man':", man.map(a => a.title));

// findById needs a real _id โ€” grab one first
const first = await Anime.findOne();
console.log("Found by _id:", (await Anime.findById(first._id)).title);

8 Query playground ๐ŸŽฎ

Type a filter, pick a method, and query a live collection of 5 anime. Click a chip to try one.
everything studio = Madhouse rating โ‰ฅ 9 before 2010 title ~ "man" โ‰ค 26 episodes Madhouse & 9+

Notice: multiple keys in one filter means AND (all must match). Operators like $gte, $lte, $lt, $in and $regex go inside a field's object.

9 Update documents

Update methods updateOne(filter, update) updateMany $set ยท $inc
Two arguments: which documents (the filter) and what to change (the update). Result tells you how many changed.
update.js
const mongoose = require("mongoose");
const Anime = mongoose.model("Anime", new mongoose.Schema({
  title: String, studio: String, rating: Number
}));

// updateOne(filter, update) โ†’ change the FIRST match
const r1 = await Anime.updateOne({ title: "One Punch Man" }, { rating: 9 });
console.log("matched:", r1.matchedCount, "ยท modified:", r1.modifiedCount);

// $inc bumps a number; updateMany changes EVERY match
const r2 = await Anime.updateMany({ rating: { $lt: 10 } }, { $inc: { rating: 1 } });
console.log("bumped", r2.modifiedCount, "ratings by 1");

const all = await Anime.find();
console.log(all.map(a => a.title + " โ†’ " + a.rating));

10 Delete documents

Delete methods deleteOne(filter) deleteMany(filter) deleteMany({}) wipes all!
delete.js
const mongoose = require("mongoose");
const Anime = mongoose.model("Anime", new mongoose.Schema({ title: String, rating: Number }));

console.log("before:", await Anime.countDocuments());

// deleteOne โ†’ first match only
const d1 = await Anime.deleteOne({ title: "Death Note" });
console.log("deleted:", d1.deletedCount);

// deleteMany โ†’ every match
const d2 = await Anime.deleteMany({ rating: { $lt: 9 } });
console.log("deleted", d2.deletedCount, "low-rated shows");

console.log("after:", await Anime.countDocuments());
console.log("remaining:", (await Anime.find()).map(a => a.title));

Danger: deleteMany({}) with an empty filter matches everything โ€” it empties the whole collection. Always double-check your filter!

11 Wire it into Express (upgrade Week 11)

Remember the Anime Collection app? Swap the JSON file for Mongoose and it's a real database app.

The controller barely changes โ€” the methods just become async and read from the model:

Week 11 โ€” array from a JSON file
before
let animeList = JSON.parse(
  fs.readFileSync(dataPath)
);

exports.index = (req, res) => {
  res.render("index", { anime: animeList });
};
Week 12 โ€” real database
after
const Anime = require("../models/Anime");

exports.index = async (req, res) => {
  const anime = await Anime.find();  // from DB
  res.render("index", { anime });
};

The model lives in its own file

models/Anime.js
const mongoose = require("mongoose");

const animeSchema = new mongoose.Schema({
  title:  { type: String, required: true },
  studio: String,
  rating: Number
});

module.exports = mongoose.model("Anime", animeSchema);

Routers โ€” split your routes into feature files

A router lets you organize routes into separate files, each responsible for one part of the app. Instead of piling every route into one giant app.js, you split them into small, focused files (e.g. routes/anime.js, routes/users.js).

Three steps: โ‘  create a router file with express.Router() ยท โ‘ก export it with module.exports ยท โ‘ข attach it in app.js with app.use().
โ‘  + โ‘ก โ€” routes/anime.js
the router file
const express = require("express");
const router = express.Router();          // โ‘  create
const c = require("../controllers/animeController");

// define this feature's routes on the router
router.get("/", c.index);
router.get("/:id", c.details);
router.post("/add", c.create);
router.post("/:id/delete", c.remove);

module.exports = router;                   // โ‘ก export
โ‘ข โ€” app.js
attach it
const animeRoutes = require("./routes/anime");

// โ‘ข attach: every route in that file is
// now prefixed with /anime
app.use("/anime", animeRoutes);

// so router.get("/:id") answers
//   GET /anime/42
// and router.post("/add") answers
//   POST /anime/add

The mount path in app.use("/anime", ...) is a prefix โ€” the router's own paths are added onto it. Mount at "/" and the paths stay as written; mount at "/anime" and they all sit under /anime.

Creating a view โ€” res.render

res.render("index", { anime }) โ€” the last step: turn data into a page.

When a controller calls res.render("index", { anime }), Express:

  1. looks in the views/ folder for index.pug;
  2. compiles it into HTML;
  3. injects the data ({ anime }) into it;
  4. sends the finished HTML to the browser.

The views are unchanged from Week 11 โ€” only the data's source changed (a database, not an array). For user input, add a form and handle its submission in an Express route, exactly like the Add form in the Anime Collection app.

12 Test your API

Tools browser (GET) Postman curl
Once your routes are wired, test them before building the UI โ€” use a browser or Postman.
To testโ€ฆUse
GET routes (read)Just visit the URL in a browser โ€” e.g. http://localhost:3000/anime.
POST / PUT / DELETEPostman (or curl) โ€” pick the method, set a JSON body, hit Send. Browsers can't easily send these by hand.

You already have a Postman-lite! The REST tester from Week 11 does exactly this โ€” pick GET/POST/PUT/DELETE, send a JSON body, read the response. Same idea Postman gives you, right in the browser.

what to check
GET  /anime        โ†’ 200  and a JSON array of documents
GET  /anime/BADID  โ†’ 404  (your "not found" handler)
POST /anime        โ†’ 201  and the new document (with its _id)
DELETE /anime/1    โ†’ 200  and { deletedCount: 1 }

13 Key takeaways

  1. A DBMS manages your data; MongoDB is a NoSQL document database โ€” JSON-like, flexible, no rigid tables.
  2. Hierarchy: database โ†’ collection โ†’ document (โ‰ˆ database โ†’ table โ†’ row). Every document gets an _id.
  3. Atlas hosts MongoDB in the cloud (free 512 MB) and hands you a connection string.
  4. Mongoose (an ODM) connects with mongoose.connect() and models data with a Schema โ†’ Model.
  5. CRUD: create/save ยท find/findOne/findById ยท updateOne/updateMany ยท deleteOne/deleteMany.
  6. Filters are objects; operators ($gte, $lt, $in, $regex, $inc) go inside a field. Every method returns a Promise โ€” so await it.
  7. In Express, keep a model file, read/write it from async controllers, and render the same Pug views. Data source changes; everything else stays.

14 Self-check quiz

No grades, instant feedback. Twelve questions โ€” the last five really dig into queries & CRUD. Can you get them all?

Q1. MongoDB is which kind of database?

MongoDB is NoSQL, document-oriented โ€” flexible JSON-like documents, no fixed tables or NULL filler.

Q2. In MongoDB, a collection is like a relationalโ€ฆ

Database โ†’ Collection โ†’ Document maps to Database โ†’ Table โ†’ Row. A document is the row.

Q3. What is Mongoose?

Mongoose is an Object Data Modeling library โ€” schemas, validation and CRUD methods on top of MongoDB.

Q4. Which finds all anime with rating 9 or higher?

$gte means "greater than or equal," and it goes inside the field's object. find returns all matches; findOne only the first.

Q5. Model.find() returnsโ€ฆ

Database calls are asynchronous. Every Mongoose query returns a Promise โ€” await it inside an async function.

Q6. Anime.deleteMany({}) โ€” empty filter โ€” does what?

An empty filter matches everything, so deleteMany({}) wipes the whole collection. Handle with care!

Q7. Moving Week 11's app to a database, what has to change in the controller?

The controller swaps the in-memory array for await Anime.find() etc. The routes and Pug views stay the same โ€” only the data source changed.

Deeper dive โ€” queries & CRUD:

Q8. What does Anime.find({ studio: "Madhouse", year: { $lt: 2010 } }) match?

Multiple keys in one filter mean AND โ€” every condition must match. So this returns only Madhouse shows from before 2010 (Death Note). To express OR you'd use $or.

Q9. Nothing matches: const r = await Anime.find({ rating: 100 }); โ€” what is r?

find() always returns an array โ€” empty when nothing matches. That's the key difference from findOne(), which returns null when there's no match. So check r.length, not r === null.

Q10. Give every anime rated below 9 a +1 rating bump. Which is correct?

updateMany changes all matches (updateOne stops at the first). Order matters: filter first (which docs), update second ($inc bumps a number). Swapping them updates nothing.

Q11. In a REST API, a POST /anime route (create a new anime) usually calls which Mongoose method?

The CRUD โ†” REST mapping: POST โ†’ create, GET โ†’ find/findById, PUT/PATCH โ†’ updateOne, DELETE โ†’ deleteOne. So a POST handler builds the document from req.body and calls create() (or save()).

Q12. You forget await: const anime = Anime.find(); then anime.map(...). What happens?

Every query returns a Promise. Without await (inside an async function) โ€” or a .then() โ€” you're holding the Promise, not the documents. This is the #1 database bug: always await your queries.

15 Reading & cheat sheet

Zybooks โ€” Chapter 10 "node.js": sections 10.11 โ€“ 10.17. Plus the MongoDB Atlas tutorial posted in Week 12.

Grab the Mongoose cheat sheet

๐Ÿƒ Mongoose & MongoDB CRUD
Mongoose and MongoDB one-page cheat sheet: connect, schema, model, create, read, update, delete and operators
Challenge: take the Week 11 Anime Collection app and swap the JSON file for a Mongoose model โ€” list, add and delete straight from a real database.