3.3 · MongoDB & Mongoose: NoSQL database

Level 3 · Express & databases

3.3MongoDB & Mongoose: NoSQL database

Goal: persist data with MongoDB and Mongoose: connection, validated schemas, models, and full async CRUD — the step turning a server into a real application.
Estimated time: 12 min · self-paced reading · If you only have 5 min: read “The essentials in 30s” + the diagram.

The essentials in 30 seconds

  • MongoDB stores documents: JSON/BSON-like objects in collections, with no rigid schema — close to JavaScript objects.
  • Mongoose adds rigour: a schema (types + required/unique validations) then a model offering manipulation methods.
  • CRUD is asynchronous: create, find/findById, findByIdAndUpdate, findByIdAndDelete with await, plus queries, populate and hooks.

Running case — Salim stores his catalogue without chaos

Salim, junior developer in Algiers, kept his catalogue in an in-memory array: everything vanished on each restart. He moves to MongoDB: connection via mongoose.connect with a fictitious working URI, product schema (required name, positive-number price, default category), Produit model, then full CRUD with await. When an email field is missing or a price is negative, Mongoose validation rejects before saving. No more fanciful data.

This same case returns in the lesson, the diagram, the script and the 48h challenge.

1. MongoDB, document-oriented database

A real app must persist its data: MongoDB, a document-oriented NoSQL database very popular with Node, stores documents (JSON/BSON-like structures) grouped in collections — no tables or rows, no rigid schema imposed by the database. A document looks like { nom: 'Théière', prix: 2400 }: flexible, nestable, natural for a JavaScript developer.

This flexibility is an asset (evolving structures, fast prototyping) but also a risk: without discipline, documents diverge (renamed fields, mixed types). Hence the Mongoose layer — and the NoSQL vs SQL choice (section 3-4) by data nature: flexible, barely relational → MongoDB; strongly linked and critical → SQL.

2. Mongoose: schemas, models, async CRUD

We work through Mongoose, an ODM wrapping MongoDB. The schema defines the expected shape with types and validations: new mongoose.Schema({ nom: String, email: { type: String, required: true, unique: true } }). The model created from the schema (mongoose.model('User', userSchema)) offers the methods: create / new + save, find / findById, findByIdAndUpdate, findByIdAndDelete — all asynchronous, hence with await.

Mongoose also handles queries (filters, sorting, pagination), relations (references + populate to join) and hooks (running code before/after, like hashing a password before saving — section 4-2). Salim’s habit: always define a schema even if MongoDB does not require it, and never a password or real URI in shared code.

Common misinterpretation: “Schema-less MongoDB means no rules at all, and Mongoose methods are synchronous”. No. Without a Mongoose schema, data quickly becomes inconsistent (diverging fields and types); the schema brings validation and clean rejections. And all CRUD methods return Promises: without await, you handle a pending promise, not the document.
MongoDB + Mongoose Document flexibility, schema rigour DocumentsJSON/BSON in collections+SchemaTypes + required/unique+Model + CRUDcreate, find, update, delete with await The schema validates before saving; CRUD is async.
Document flexibility, schema rigour
Text version of the diagram (SEO / screen readers / translation): 1) Documents: Salim stores his products as flexible objects in a collection. 2) Schema: he enforces a required name and positive price via Mongoose. 3) Model + CRUD: create, findById, update and delete with await, plus populate for linked reviews. Lesson: NoSQL flexibility lives under schema control.

🔎 Concrete example — Schema, model and validated CRUD

Salim persists his first product with validation.

const mongoose = require('mongoose');
// FICTITIOUS URI for teaching — never a real URI in shared code
await mongoose.connect('mongodb://127.0.0.1:27017/demo-catalogue');
const produitSchema = new mongoose.Schema({
  nom: { type: String, required: true },
  prix: { type: Number, min: 0 }
});
const Produit = mongoose.model('Produit', produitSchema);
const p = await Produit.create({ nom: 'Théière', prix: 2400 });
const lu = await Produit.findById(p._id);

Reading: connection, validated schema, model, then create/findById with await.

Lesson: invalid data is rejected by the schema before the database.

Section vocabulary

Persistence
Durable saving of data in a database (users, products…) instead of volatile memory.

Simple example: Salim’s catalogue kept after restart.

What it is not: a JS array emptied on each server stop.

Reflex: persist as soon as data must survive.

MongoDB (document NoSQL)
Database storing JSON/BSON-like documents in collections, with no rigid schema; flexible and close to JS.

Simple example: The products collection with varied documents.

What it is not: a SQL table with strict columns and joins.

Reflex: consider it for flexible, evolving data.

Mongoose (ODM)
Practical layer over MongoDB: validated schemas, models, queries, relations and hooks.

Simple example: Salim’s product schema with required and min.

What it is not: the raw driver with no validation or models.

Reflex: go through Mongoose for everyday rigour.

Schema & model
The schema defines structure and validations; the model, created from the schema, offers the CRUD methods.

Simple example: produitSchema then mongoose.model('Produit', produitSchema).

What it is not: documents thrown into the database with no declared shape.

Reflex: declare types, required and unique before the first create.

Async CRUD
Create, Read, Update, Delete via asynchronous Mongoose methods (await): create, find, findByIdAndUpdate, findByIdAndDelete.

Simple example: await Produit.create(...) then findById.

What it is not: imaginary synchronous calls with no await.

Reflex: systematic await and try/catch error handling.

In practice — Persist data with MongoDB and Mongoose

  1. Connect to MongoDB with mongoose.connect(uri) (local database or MongoDB Atlas in the cloud, fictitious URI in exercises).
  2. Define a schema (types + validations: required, unique) and create a model with mongoose.model('User', userSchema).
  3. Perform a full CRUD (asynchronous, with await): create (create), find/findById (read), findByIdAndUpdate (update), findByIdAndDelete (delete).
  4. Explore queries (filters, sorting, pagination) and one relation between documents (reference + populate).
You persist data with MongoDB and Mongoose: connection, validated schemas, models, and full async CRUD — the step turning a server into a real application.

Ready-to-say script — presenting your Mongo persistence (30 s)

“The catalogue is persisted under MongoDB: mongoose connection, product schema with required name and positive price, Produit model, full CRUD with await. Validation rejects invalid data before the database, and I test on a fictitious local URI, never a real secret in the code.”

Say in demo; show one create rejected by validation.

Copy-paste template — Mongoose CRUD (fictitious URI)

const mongoose = require('mongoose');
// FICTITIOUS URI — teaching example, replace locally, secret in environment variable
const URI_FACTICE = 'mongodb://127.0.0.1:27017/demo-catalogue';
async function main() {
  await mongoose.connect(URI_FACTICE);
  const produitSchema = new mongoose.Schema({
    nom: { type: String, required: true },
    prix: { type: Number, min: 0, required: true }
  });
  const Produit = mongoose.model('Produit', produitSchema);
  const cree = await Produit.create({ nom: 'Théière', prix: 2400 });
  const lu = await Produit.findById(cree._id);
  await Produit.findByIdAndUpdate(cree._id, { prix: 2600 });
  await Produit.findByIdAndDelete(cree._id);
  await mongoose.disconnect();
}
main().catch(console.error);

Freelancer

One validated Mongoose model is enough to make stored quotes reliable.

SME in Algiers

Salim models linked products and reviews, with populate to display reviews per product.

Product team

Enforce required/unique at the schema so each team writes consistent data.

Beginner

This week: a two-field schema and a create + find CRUD with await.

Caution: Connection and secrets: use a fictitious or local URI to learn (e.g. mongodb://127.0.0.1:27017/demo-catalogue) and never paste a real URI or password into code, screenshots or messages. In production, the URI lives in an environment variable outside the repository, with a minimally-privileged user. If accidentally exposed, regenerate the secret and change the password before any reconnection.

✓ Best practices

  • Always define a Mongoose schema (rigour + validation).
  • Use async CRUD with await.
  • Hash passwords via a hook before saving (see 4.2).

✗ Common pitfalls

  • Giving up the schema because MongoDB is “flexible” (chaotic data).
  • Accepting any field sent by a client (field-injection risk).
  • Forgetting Mongoose methods are asynchronous (await).

Key takeaways

  • A real application PERSISTS its data. Two families: NoSQL (MongoDB) and SQL/relational (3-4).
  • MongoDB (DOCUMENT-oriented NoSQL) stores JSON/BSON-like documents in COLLECTIONS — flexible, close to JS objects.
  • MONGOOSE (ODM) adds schemas (structure + VALIDATION) and models (methods): rigour AND flexibility.
  • Async CRUD (await): create / find(ById) / findByIdAndUpdate / findByIdAndDelete; also queries, relations (populate), hooks.

48h challenge — apply tomorrow

Define 1 Mongoose schema with 2 validations and perform a create + find CRUD with await.

Section exit checklist
  • I tell document/collection apart from table/row.
  • My schema validates required and types.
  • My CRUD is async with await.
Self-rating 1-5: is my invalid data rejected by the schema?are my secrets out of the code (fictitious URI)?

Frequently asked questions

MongoDB (NoSQL) or SQL: how to choose?

By data nature. MongoDB suits flexible, evolving data, JSON-like nested structures, fast prototyping and barely relational data. SQL suits strongly relational data (many joins), critical integrity (strict schema, ACID transactions) and complex cross-table queries. Neither is universally better: Salim starts in MongoDB for his flexible catalogue and keeps SQL (3.4) for linked invoicing.

Why use Mongoose rather than MongoDB directly, and are schemas needed?

Mongoose brings schemas + validation (rejecting inconsistent data before saving), elegant models and methods (CRUD, filters, sorting, pagination, populate), hooks (hashing a password before save) and a consistent API. Yes, define schemas even if MongoDB does not require them: without them, renamed fields and mixed types guarantee chaos. Flexibility remains (optional fields, nested structures), but under control.

Check your understanding

What does the Mongoose schema do for MongoDB data?

Additional resources (optional, to go further)

Level 3 examFresh, harder questions to test what you've learned