FSD5
FSD5
Module-05
MangoDB
MangoDB Basics
Core Concepts:
Documents:
The basic unit of data storage in MongoDB, similar to a row in relational
databases but in JSON format.
{
"invoiceNumber": 1234,
"invoiceDate": ISODate("2018-10-12T05:17:15.737Z"),
"billingAddress": {
"name": "Acme Inc.",
"line1": "106 High Street",
"city": "New York City",
Page 1
BIS601 | FULLSTACK DEVELOPMENT |
"zip": "110001-1234"
},
"items": [
{
"description": "Compact Fluorescent Lamp",
"quantity": 4,
"price": 12.48
},
{
"description": "Whiteboard",
"quantity": 1,
"price": 5.44
}
]
}
1. Basic Fields
o "invoiceNumber": 1234 → Stores an invoice number as a number.
o "invoiceDate": ISODate("2018-10-12T05:17:15.737Z") → Uses
MongoDB's date format for timestamps.
2. Nested Object (billingAddress)
o Instead of storing address details in a separate table (like in SQL),
MongoDB stores it inside the document.
o "billingAddress" contains fields like name, line1, city, and zip.
3. Array of Objects (items)
o Instead of using a separate table for invoice items, MongoDB allows
an array of objects within the document.
o Each item contains "description", "quantity", and "price".
Faster Reads – No need for complex joins; all data is retrieved in a single query.
Flexible Schema – No need to define a fixed structure; fields can be added or
modified easily.Efficient Storage – Stores related data together, reducing redundancy.
Better Scalability – Easily scales horizontally across multiple servers.
Page 2
BIS601 | FULLSTACK DEVELOPMENT |
Collections:
A group of related documents, similar to tables in SQL databases.
Page 3
BIS601 | FULLSTACK DEVELOPMENT |
"email": "alice@example.com",
"age": 30,
"address": {
"street": "123 Main St",
"city": "New York",
"zip": "10001"
}
}
Here,
Databases
A database is a logical grouping of many collections. Since there are no
foreign keys like in a SQL database, the concept of a database is nothing but
a logical partitioning namespace.
Most database operations read or write from a single collection, but
$lookup, which is a stage in an aggregation pipeline, is equivalent to a join
in SQL databases.
This stage can combine documents within the same database. Further, taking
backups and other administrative tasks work on the database as a unit.
A database connection is restricted to accessing only one database, so to
access multiple databases, multiple connections are required.
Thus, it is useful to keep all the collections of an application in one
database, though a database server can host multiple databases
Query Language
Unlike the universal English-like SQL in a relational database, the
MongoDB query language is made up of methods to achieve various
operations.
Page 4
BIS601 | FULLSTACK DEVELOPMENT |
The main methods for read and write operations are the CRUD methods.
Other methods include aggregation, text search, and geospatial queries.
The query filter is a JavaScript object consisting of zero or more properties,
where the property name is the name of the field to match on and the
property value consists of another object with an operator and a value.
For example, to match all documents with the field invoiceNumber that are
greater than 1,000, the following query filter can be used:
Since there is no "language" for querying or updating, the query filters can
be very easily constructed programmatically.
Unlike relational databases, MongoDB encourages denormalization, that is,
storing related parts of a document as embedded subdocuments rather than
as separate collections (tables) in a relational database.
Take an example of people (name, gender, etc.) and their contact
information (primary address, secondary address etc.).
In a relational database, this would require separate tables for People and
Contacts, and then a join on the two tables when all of the information is
needed together.
In MongoDB, on the other hand, it can be stored as a list of contacts within
the same People document. That’s because a join of collections is not
natural to most methods in MongoDB: the most convenient find() method
can operate only on one collection at a time.
Installation
Before you try to install MongoDB on your computer, you may want to try out
one of the hosted services that give you access to MongoDB. There are many
services, but the following are popular and have a free version that you can use
for a small test or sandbox application. Any of these will do quite well for the
purpose of the Issue Tracker application that we’ll build as part of this book .
Page 5
BIS601 | FULLSTACK DEVELOPMENT |
On a Windows system, you may need to append .exe to the command. The
command may require a path depending on your installation method. If the shell
starts successfully, it will also connect to the local MongoDB server instance.
You should see the version of MangoDB printed on the console, the database it
is connecting to (the default is test), and a command prompt, like this, if you
had installed MongoDB version 4.0.2 locally:
Page 6
BIS601 | FULLSTACK DEVELOPMENT |
This will list the databases and the storage occupied by them. For example, in a
fresh local installation of MongoDB, this is what you will see:
These are system databases that MongoDB uses for its internal book keeping, etc.
We will not be using any of these to create our collections, so we’d better change
the current database. To identify the current database, the command is:
> db
The default database a mongo shell connects to is called test and that is what you
are likely to see as the output to this command. Let’s now see what collections
exist in this database.
You will find that there are no collections in this database, since it is a fresh
installation. Further, you will also find that the database test was not listed when
we listed the available databases. That’s because databases and collections are
really created only on the first write operation to any of these. Let’s switch to a
database called issuetracker instead of using the default database:
This should result in output that confirms that the new database is issuetracker:
This command should return nothing. Now, let’s create a new collection. This is
done by creating one document in a collection.
Page 7
BIS601 | FULLSTACK DEVELOPMENT |
Apart from the insertOne() method, many methods are available on any collection.
You can see the list of available methods by pressing the Tab character twice after
typing "db.employees." (the period at the end is required before pressing Tab). You
may find an output like the following:
This is the auto-completion feature of the mongo shell at work. Note that you can
let the mongo shell auto-complete the name of any method by pressing the Tab
character after entering the beginning few characters of the method.
Page 8
BIS601 | FULLSTACK DEVELOPMENT |
Aggregate
Aggregation in MongoDB is a process of transforming and analyzing data by
applying operations such as filtering, grouping, and computations. It allows us to
summarize and manipulate data similar to SQL’s GROUP BY but with additional
capabilities such as joins ($lookup), array expansion ($unwind), and complex
transformations.
The find() method retrieves raw documents, but aggregate() allows for
summarization.
1. Summing a Field
db.employees.aggregate([
{ $group: { _id: null, total_age: { $sum: '$age' } } }
Page 9
BIS601 | FULLSTACK DEVELOPMENT |
])
Output:
db.employees.aggregate([
{ $group: { _id: null, count: { $sum: 1 } } }
])
Output:
Let’s group employees by their organization and calculate the total age for each:
db.employees.aggregate([
{ $group: { _id: '$organization', total_age: { $sum: '$age' } } }
])
Output:
4. Calculating Average
db.employees.aggregate([
{ $group: { _id: '$organization', average_age: { $avg: '$age' } } }
])
Output:
Page 10
BIS601 | FULLSTACK DEVELOPMENT |
To connect to MongoDB, you need to use the MongoClient object. The connection
URL specifies the server address and the database.
Page 11
BIS601 | FULLSTACK DEVELOPMENT |
Local MongoDB:
mongodb://localhost/issuetracker
mongodb+srv://UUU:PPP@cluster0-
XXX.mongodb.net/issuetracker?retryWrites=true
o Replace UUU with the username, PPP with the password, and XXX
with the cluster hostname.
Once connected, we can interact with collections using the database object (db).
Page 12
BIS601 | FULLSTACK DEVELOPMENT |
Page 13
BIS601 | FULLSTACK DEVELOPMENT |
The callback-based approach can lead to "callback hell" due to deeply nested
functions. A cleaner approach is using async/await.
try {
await client.connect(); // Wait for the connection to establish
console.log('Connected to MongoDB');
const db = client.db();
const collection = db.collection('employees');
} catch (err) {
console.error("Error:", err);
Page 14
BIS601 | FULLSTACK DEVELOPMENT |
} finally {
client.close();
}
}
node scripts/trymongo.js
6. Error Handling
if (err) {
console.error("Error:", err);
client.close();
return;
}
Page 15
BIS601 | FULLSTACK DEVELOPMENT |
try {
await client.connect();
} catch (err) {
console.error("Connection error:", err);
}
Schema Initialization,
MongoDB is a NoSQL database, meaning it does not enforce a fixed schema like
relational databases (SQL-based). However, initializing a schema in MongoDB
typically refers to setting up indexes and inserting initial data into collections.
Since MongoDB does not require predefined schemas, schema initialization in this
context means:
Before inserting fresh data, we clear the issues collection to remove any existing
documents:
Page 16
BIS601 | FULLSTACK DEVELOPMENT |
db.issues.remove({});
const issuesDB = [
{
id: 1, status: 'New', owner: 'Ravan', effort: 5,
created: new Date('2019-01-15'), due: undefined,
title: 'Error in console when clicking Add',
},
{
id: 2, status: 'Assigned', owner: 'Eddie', effort: 14,
created: new Date('2019-01-16'), due: new Date('2019-02-01'),
title: 'Missing bottom border on panel',
},
];
db.issues.insertMany(issuesDB);
This helps developers by providing sample data for testing the application.
3. Create Indexes
Indexes are essential for improving query performance. The script creates indexes
on:
Page 17
BIS601 | FULLSTACK DEVELOPMENT |
mongo mongodb+srv://user:pwd@xxx.mongodb.net/issuetracker
scripts/init.mongo.js
mongo mongodb://user:pwd@xxx.mlab.com:33533/issuetracker
scripts/init.mongo.js
Instead of opening and closing the database connection for each query, we
maintain a global connection variable (db). This improves efficiency and ensures
smooth handling of multiple API requests.
Page 18
BIS601 | FULLSTACK DEVELOPMENT |
We use the MongoClient from the mongodb package to connect to the database
asynchronously:
let db;
Page 19
BIS601 | FULLSTACK DEVELOPMENT |
app.listen(3000, function () {
console.log('App started on port 3000');
});
} catch (err) {
console.log('ERROR:', err);
}
})();
This ensures:
Since MongoDB automatically adds an _id field to each document, we update the
GraphQL schema to include this field:
type Issue {
_id: ID!
id: Int!
...
}
Page 20
BIS601 | FULLSTACK DEVELOPMENT |
Writing to MangoDB
To fully integrate MongoDB into the application, we need to update the Create
API so that it writes new issues directly to the database instead of storing them in
an in-memory array.
To increment and fetch the latest issue ID, we define getNextSequence() using
MongoDB’s findOneAndUpdate() method.
Page 21
BIS601 | FULLSTACK DEVELOPMENT |
Page 22
BIS601 | FULLSTACK DEVELOPMENT |
Since we now store issues in MongoDB, we can remove the in-memory array:
node server.js
mongo
use issuetracker
db.issues.find().pretty()
This should display all issues, including the newly created one.
Page 23
BIS601 | FULLSTACK DEVELOPMENT |
Back-End Modules
Backend modules help organize and structure code for maintainability, scalability,
and reusability. In a Node.js application, modularization is achieved using the
require() function for importing and module. exports for exporting functionalities.
Below is an explanation of key modules used in the Issue Tracker API, along with
how they interact.
Page 24
BIS601 | FULLSTACK DEVELOPMENT |
This module defines a custom GraphQL scalar type for handling date values. It:
Code:
module.exports = GraphQLDate;
This module manages the "about" message, which describes the API version. It:
Code:
function getMessage() {
return aboutMessage;
}
Page 25
BIS601 | FULLSTACK DEVELOPMENT |
Code:
require('dotenv').config();
const { MongoClient } = require('mongodb');
let db;
function getDb() {
return db;
}
Page 26
BIS601 | FULLSTACK DEVELOPMENT |
Code:
This module sets up the GraphQL schema and resolver functions. It:
Code:
Page 27
BIS601 | FULLSTACK DEVELOPMENT |
const resolvers = {
Query: {
about: about.getMessage,
issueList: issue.list,
},
Mutation: {
setAboutMessage: about.setMessage,
issueAdd: issue.add,
},
GraphQLDate,
};
function installHandler(app) {
server.applyMiddleware({ app, path: '/graphql' });
}
module.exports = { installHandler };
Code:
require('dotenv').config();
const express = require('express');
const { connectToDb } = require('./db.js');
const { installHandler } = require('./api_handler.js');
Page 28
BIS601 | FULLSTACK DEVELOPMENT |
(async function () {
try {
await connectToDb();
app.listen(port, () => console.log(`API server started on port ${port}`));
} catch (err) {
console.error('ERROR:', err);
}
})();
To solve this, tools like Webpack and Browserify automate dependency resolution,
allowing developers to write modular code while bundling everything into
optimized JavaScript files.
1. Maintainability:
2. Dependency Management:
3. Performance Optimization:
Page 29
BIS601 | FULLSTACK DEVELOPMENT |
4. Scalability:
Setting Up Webpack
cd ui
npm install --save-dev webpack@4 webpack-cli@3
However, Webpack gives a warning about the missing mode option. To remove
the warning, specify development mode:
Instead of writing everything in one file, we split the code into separate modules.
Page 30
BIS601 | FULLSTACK DEVELOPMENT |
graphQLFetch.js
Exporting Modules
o Imported as:
Page 31
BIS601 | FULLSTACK DEVELOPMENT |
o Imported as:
<script src="/env.js"></script>
<script src="/app.bundle.js"></script>
This ensures the browser loads the bundled file containing all required modules.
Page 32
BIS601 | FULLSTACK DEVELOPMENT |
3. Running Webpack
o Compile once:
npx webpack
Page 33
BIS601 | FULLSTACK DEVELOPMENT |
To manage these libraries within our project, we install them using npm:
$ cd ui
$ npm install react@16 react-dom@16
$ npm install prop-types@15
$ npm install whatwg-fetch@3
$ npm install babel-polyfill@6
After installation, we import these libraries in our React files instead of relying on
CDNs.
Page 34
BIS601 | FULLSTACK DEVELOPMENT |
module.exports = {
mode: 'development',
entry: { app: './src/App.jsx' },
output: {
filename: '[name].bundle.js', // Generates app.bundle.js and
vendor.bundle.js
path: path.resolve( dirname, 'public'),
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: 'babel-loader',
},
],
},
optimization: {
splitChunks: {
name: 'vendor',
chunks: 'all',
},
},
Page 35
BIS601 | FULLSTACK DEVELOPMENT |
};
4. Updating index.html
o Remove CDN links.
o Include vendor.bundle.js before app.bundle.js.
<script src="/vendor.bundle.js"></script>
<script src="/app.bundle.js"></script>
Benefits of Splitting Bundles
Webpack’s watch mode detects file changes and recompiles, but you must
manually refresh the browser to see updates.
Refreshing too soon might load an outdated bundle.
Requires an extra terminal for running npm run watch.
HMR updates only the changed modules in the browser without refreshing the
entire page, preserving application state.
Key Benefits:
Page 36
BIS601 | FULLSTACK DEVELOPMENT |
2. Modify webpack.config.js
o Change entry point to an array to add HMR support:
config.entry.app.push('webpack-hot-middleware/client');
config.plugins.push(new webpack.HotModuleReplacementPlugin());
config.entry.app.push('webpack-hot-middleware/client');
config.plugins = config.plugins || [];
config.plugins.push(new webpack.HotModuleReplacementPlugin());
if (module.hot) {
module.hot.accept();
Page 37
BIS601 | FULLSTACK DEVELOPMENT |
By default, HMR reloads the entire React component tree, losing local
state.
Solution: Use react-hot-loader (not implemented in this case).
Debugging DefinePlugin:
The unpleasant thing about compiling files is that the original source code
gets lost, and if you have to put breakpoints in the debugger, it’s close to
impossible, because the new code is hardly like the original.
Creating a bundle of all the source files makes it worse, because you don’t
even know where to start. Fortunately, Webpack solves this problem by its
ability to give you source maps, things that contain your original source code
as you typed it in.
The source maps also connect the line numbers in the transformed code to
your original code. Browsers’ development tools understand source maps
and correlate the two, so that breakpoints in the original source code are
converted breakpoints in the transformed code.
Webpack configuration can specify what kind of source maps can be created
along with the compiled bundles. A single configuration parameter called
devtool does the job.
The kind of source maps that can be produced varies, but the most accurate
(and the slowest) is generated by the value source-map. For this application,
Page 38
BIS601 | FULLSTACK DEVELOPMENT |
because the UI code is small enough, this is not discernably slow, so let’s
use it as the value for devtool. The changes to webpack.config.js in the UI
directory are shown in Listing 8-29
As you can see, apart from the package bundles, there are accompanying maps
with the extension .map. Now, when you look at the browser’s Developer Console,
you will be able to see the original source code and be able to place breakpoints in
it. A sample of this in the Chrome browser is shown in Figure 8-1
Page 39
BIS601 | FULLSTACK DEVELOPMENT |
If you are using the Chrome or Firefox browser, you will also see a message in the
console asking you to install the React Development Tools add-on. You can find
installation instructions for these browsers at
https://reactjs.org/blog/2015/09/02/new-react-developer-tools.html. This addon
provides the ability to see the React components in a hierarchical manner like the
DOM inspector. For example, in the Chrome browser, you’ll find a React tab in the
developer tools. Figure 8-2 shows a screenshot of this add-on.
Page 40
BIS601 | FULLSTACK DEVELOPMENT |
Page 41
BIS601 | FULLSTACK DEVELOPMENT |
Production Optimization
1 Bundle Size & Performance
Use content hashes in script file names to force browsers to load new
versions.
Webpack can generate unique file names based on content changes.
Page 42
BIS601 | FULLSTACK DEVELOPMENT |
Page 43