Skip to main content
Published / updated

NoSQL Foundations and MongoDB

Before you start

You need: programming basics and JSON familiarity. SQL (Track 06) helps for the comparisons but is not required.

You need installed: MongoDB, plus MongoDB Compass (the GUI) and mongosh (the shell).

Time: about 45 minutes, plus the practice.

Learning objective

Explain what a document database stores, how it differs from a relational one, and judge which suits a given problem.

Topics

  • What NoSQL means
  • SQL versus document databases
  • Databases, collections and documents
  • JSON and BSON
  • Installing MongoDB
  • Compass and mongosh
  • The _id field
  • When to choose which

What NoSQL means

"NoSQL" covers several unrelated database families. The one this track teaches is the document store.

FamilyStoresExample
DocumentJSON-like documentsMongoDB
Key-valueValues by keyRedis
Column-familyWide sparse rowsCassandra
GraphNodes and edgesNeo4j

The name is misleading — MongoDB has a rich query language, and modern SQL databases store JSON. The real distinction is the data model: fixed rows and columns versus flexible documents.

This track assumes SQL Server first. Relational modelling, joins, constraints and transactions are the reference point, and MongoDB is easier to judge once you know what you are trading away.

SQL versus document

-- Relational: a student across three tables
Student (Id, SchoolId, Name, RollNumber, ClassName, Section)
Address (Id, StudentId, Line1, City, PinCode)
Subject (Id, StudentId, Name, Marks)
// Document: one student, one document
{
_id: ObjectId("6650a1b2c3d4e5f6a7b8c9d0"),
schoolId: 1,
name: "Ravi Kumar",
rollNumber: "NCA-2024-0012",
className: "10th",
section: "A",
address: {
line1: "12 MG Road",
city: "Hyderabad",
pinCode: "500001"
},
subjects: [
{ name: "Mathematics", marks: 87 },
{ name: "Science", marks: 72 }
]
}

Reading a student with their address and subjects is three joins in SQL and one document read in MongoDB. That is the core trade.

SQLDocument
StructureFixed schema, enforcedFlexible per document
Related dataNormalised, joinedOften embedded
Reading one entitySeveral joinsOne read
Reporting across entitiesNaturalHarder
ConstraintsForeign keys, CHECK, UNIQUEUnique indexes and validation rules only
TransactionsMature, everywhereSupported, less commonly used
Schema changesA migrationWrite the new shape

Terminology

SQLMongoDB
DatabaseDatabase
TableCollection
RowDocument
ColumnField
Primary key_id
IndexIndex
Join$lookup (or embedding)
SELECTfind
INSERTinsertOne / insertMany
UPDATEupdateOne / updateMany
DELETEdeleteOne / deleteMany

Flexible schema

Two documents in one collection need not match:

{ _id: 1, name: "Ravi Kumar", className: "10th" }
{ _id: 2, name: "Priya Sharma", className: "10th", section: "A", scholarship: true }

That flexibility is genuinely useful and genuinely dangerous.

Useful: adding a field needs no migration, and optional data costs nothing.

Dangerous: nothing stops className: "10th" in one document and class_name: 10 in another. A query on className silently misses half the collection, and no error is raised.

Flexible schema does not mean no schema. It means the schema lives in your application code and your discipline rather than in the database — so a code review is the only thing enforcing it, unless you add validation rules.

Databases, collections and documents

MongoDB deployment
└── Database nexcoding_school
└── Collection students
└── Document { _id: ..., name: "Ravi Kumar", ... }
└── Field name, rollNumber, className

Databases and collections are created lazily — on first write, not by a CREATE statement:

use nexcoding_school // does not exist yet
db.students.insertOne({ name: "Ravi Kumar" }) // now both exist

That is convenient and it is why a typo in a collection name creates a second, empty collection rather than failing. db.studnets.find() returns nothing and reports no error.

Check show collections when a query returns nothing unexpectedly.

JSON and BSON

MongoDB stores BSON — binary JSON — which adds types JSON lacks.

BSON typeNotes
ObjectIdThe default _id — 12 bytes, includes a timestamp
DateA real date, not a string
Int32 / Int64Distinct integer widths
DoubleFloating point
Decimal128Exact decimal — use for money
Boolean, String, Array, Object, NullAs in JSON
BinaryBinary data
{
_id: ObjectId("6650a1b2c3d4e5f6a7b8c9d0"),
admittedOn: ISODate("2024-06-15T00:00:00Z"),
totalFees: NumberDecimal("50000.00"),
marks: NumberInt(87),
isActive: true
}

Use NumberDecimal for money. The default numeric type is Double, which carries the same binary rounding error as float everywhere else — fee totals drift and receipts stop reconciling.

Store dates as ISODate, not strings. A string date cannot be compared with $gt, cannot be sorted correctly across formats, and cannot use date operators. This is the most common modelling mistake.

// Wrong
{ admittedOn: "2024-06-15" }

// Right
{ admittedOn: ISODate("2024-06-15T00:00:00Z") }

Type mismatches are silent:

db.students.insertOne({ rollNumber: "NCA-2024-0012" })
db.students.insertOne({ rollNumber: 20240012 }) // a number

db.students.find({ rollNumber: "NCA-2024-0012" }) // finds only the first

MongoDB does not coerce. A field stored as a string and queried as a number matches nothing, with no error — the single most common cause of "my query returns no results".

The _id field

Every document has _id. MongoDB generates an ObjectId when you do not supply one.

ObjectId("6650a1b2c3d4e5f6a7b8c9d0")
// └──────┘└────┘└──┘└────┘
// timestamp machine pid counter
PropertyDetail
UniqueWithin the collection
ImmutableCannot be changed after insert
IndexedAutomatically, and the index cannot be dropped
Sortable by timeThe first four bytes are a timestamp
ObjectId("6650a1b2c3d4e5f6a7b8c9d0").getTimestamp()

Sorting by _id is approximately sorting by creation time, which is often enough to avoid a separate createdAt field.

You may supply your own:

db.students.insertOne({ _id: "NCA-2024-0012", name: "Ravi Kumar" })

Use a natural key as _id only when it genuinely never changes. A roll number that gets renumbered means every reference to it must change, and _id is immutable — so the fix is deleting and reinserting the document.

An ObjectId is not a string. This is a constant source of confusion:

db.students.find({ _id: "6650a1b2c3d4e5f6a7b8c9d0" }) // finds nothing
db.students.find({ _id: ObjectId("6650a1b2c3d4e5f6a7b8c9d0") }) // correct

An id arriving from a URL or a JSON body is a string and must be converted before querying.

Installing

Local, for development:

Download MongoDB Community Server from mongodb.com and install it as a service. Install MongoDB Compass — the GUI — and mongosh, the shell.

Atlas, the hosted option, has a free tier and is the fastest way to start. It requires adding your IP to an access list; forgetting to is why a connection times out with no useful message.

Docker, for a disposable local instance:

docker run -d --name mongo -p 27017:27017 mongo:7
mongodb://localhost:27017
mongodb://user:password@localhost:27017/nexcoding_school
mongodb+srv://user:password@cluster.mongodb.net/nexcoding_school

mongodb+srv:// is the Atlas form — it resolves the cluster's hosts from DNS.

Never commit a connection string. It contains the password, and Git history is permanent.

Compass and mongosh

Compass — connect, browse collections, edit documents, build queries visually, view indexes and explain plans. The Schema tab samples a collection and reports which fields exist, their types and how often each appears. That is the fastest way to find inconsistent data in a flexible-schema collection.

mongosh — the shell, and a full JavaScript environment:

mongosh "mongodb://localhost:27017"
show dbs
use nexcoding_school
show collections

db.students.countDocuments()
db.students.findOne()
db.students.find().limit(5)
db.students.find().pretty()

db.students.drop()
db.dropDatabase()

use x switches database and creates it lazily. db is the current database.

db.students.insertMany([
{ schoolId: 1, name: "Ravi Kumar", rollNumber: "NCA-2024-0012",
className: "10th", section: "A", marks: NumberInt(87) },
{ schoolId: 1, name: "Priya Sharma", rollNumber: "NCA-2024-0018",
className: "10th", section: "A", marks: NumberInt(91) },
{ schoolId: 1, name: "Arjun Reddy", rollNumber: "NCA-2024-0031",
className: "9th", section: "B", marks: NumberInt(65) }
])

Because mongosh is JavaScript, scripting works:

db.students.find({ className: "10th" }).forEach(s => print(s.name))

const total = db.students.find({ className: "10th" })
.toArray()
.reduce((sum, s) => sum + s.marks, 0)
mongosh "mongodb://localhost:27017/nexcoding_school" --file seed.js

mongosh --file runs a script — the equivalent of sqlcmd -i, and the right way to seed a development database reproducibly.

When to choose which

Document databases suit:

  • Data naturally read as one unit — a student with their address and subjects
  • Shapes that vary between records
  • Rapidly changing requirements early in a project
  • Catalogues, content, event logs, session data

Relational databases suit:

  • Data queried across entities in many combinations
  • Reporting and ad-hoc analysis
  • Rules that must be guaranteed — foreign keys, uniqueness, CHECK constraints
  • Anything financial

For the School Management System, SQL Server is the better fit, and it is worth being explicit about why:

  • Fee payments need transactions and exact decimal arithmetic
  • Exam results are queried across students, subjects and exams in every combination
  • RollNumber must be unique per school, and a CHECK must prevent an absent student having marks
  • Reporting is the main workload

A document database can do all of that, with more application code and fewer guarantees.

Where MongoDB would fit in the same system: an audit log, notification records, uploaded document metadata, or a flexible "additional details" area whose shape differs per school. Using both is a normal and sensible design.

Do not choose a database because it is newer. Choose it because the data model matches how the data is written and read.

Errors you will hit

MessageCauseFix
MongoNetworkError: connect ECONNREFUSEDService not running, or wrong portStart MongoDB; default port is 27017
Authentication failedWrong credentials or auth databaseCheck the connection string
A query returns nothing but the data is thereWrong database or collection selecteduse <db>; show collections
TypeError: db.students.find is not a functionTypo in the collection nameNames are case-sensitive
Numbers come back as Double unexpectedlyInserted without a typeUse NumberDecimal for money

Collection names are case-sensitive and created on first write. A typo does not error — it silently makes a new, empty collection.

Common mistakes

  • Treating flexible schema as no schema
  • Inconsistent field names across documents
  • The same field stored as a string in some documents and a number in others
  • Storing dates as strings
  • Using Double for money instead of Decimal128
  • Querying _id with a string instead of an ObjectId
  • A typo creating a new empty collection silently
  • A natural key as _id when it can change
  • Committing a connection string
  • Choosing MongoDB for data that needs transactions and constraints

Practice

The course exercise is create a document collection.

  1. Install MongoDB locally or create an Atlas free-tier cluster. Connect with Compass and mongosh.
  2. Create nexcoding_school and insert five students with insertMany.
  3. Run show dbs before and after the first insert. Confirm lazy creation.
  4. Query a misspelled collection name. Confirm no error and no results, then run show collections.
  5. Insert one student with rollNumber as a string and one as a number. Query for the string and confirm only one matches.
  6. Insert totalFees as a plain number and as NumberDecimal. Add each to itself repeatedly and compare.
  7. Store admittedOn as a string and as ISODate. Try $gt against a date on both.
  8. Find a document, copy its _id, and query with the raw string. Confirm nothing is found, then wrap it in ObjectId().
  9. Call .getTimestamp() on an ObjectId and confirm it matches the insert time.
  10. Insert a document with your own string _id. Try to change it with updateOne.
  11. Insert two documents with different field names for the same concept. Open the Schema tab in Compass and observe the report.
  12. Write a seed.js and run it with mongosh --file.
  13. Write down, for the School Management System, which parts you would keep in SQL Server and which you would model as documents. Justify each.

Exercises 5, 7 and 8 correspond to the three most common "my query returns nothing" causes.

You can now

  • Explain what a document database stores
  • Connect with Compass and mongosh
  • Say how collections and documents map to tables and rows
  • Select the right database and list its collections
  • Say why money needs NumberDecimal

Review questions

  1. What is the practical difference between a flexible schema and no schema?
  2. Why must money use Decimal128 rather than the default number type?
  3. Why does querying _id with a string return nothing?
  4. Name two parts of a school system that suit SQL and two that suit documents.

Next: CRUD operations