23
Populating MongoDB schema
We'll be creating only the backend of the application for understanding how to post data in to a MongoDB schema that references another schema.
How can we get the following JSON data with the user schema referencing the todo schema?
{
"todo": [
{
"_id": "61023642610b8d4ce4f56f81",
"title": "test-title-1",
"description": "test-description-1",
"__v": 0
},
{
"_id": "6102365b610b8d4ce4f56f84",
"title": "test-title-2",
"description": "test-description-2",
"__v": 0
}
],
"_id": "6102361f610b8d4ce4f56f7f",
"name": "test-user",
"__v": 0
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// models/user.js | |
const mongoose = require("mongoose"); | |
const { Schema } = mongoose; | |
const UserSchema = new Schema({ | |
name: { | |
type: String, | |
required: true, | |
}, | |
todo: [{ type: Schema.Types.ObjectId, ref: "Todo" }], | |
}); | |
const User = mongoose.model("User", UserSchema); | |
module.exports = User; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// models/todo.js | |
const mongoose = require("mongoose"); | |
const { Schema } = mongoose; | |
const TodoSchema = new Schema({ | |
title: { type: String, required: true }, | |
description: { type: String }, | |
}); | |
const Todo = mongoose.model("Todo", TodoSchema); | |
module.exports = Todo; |
Here, the
User
schema is referencing the Todo
schema. To get the JSON data with the todo
data we need to do the followingObjectId
of the new todo
to the todo
array of the User
. At this stage the data will look something like this.
{
"todo": ["61023642610b8d4ce4f56f81", "6102365b610b8d4ce4f56f84"],
"_id": "6102361f610b8d4ce4f56f7f",
"name": "test-user",
"__v": 0
}
Todo
table using the populate
method which will get the data of the todo
.It is like joining two tables in
SQL
where User
table references the Todo
table using the primary key
of the Todo table
. Here, the primary key
of the Todo table
is the ObjectId
.npm
and install necessary packages.Node
and Express
.Todo
.create
user and todo and read
user and todo.API
routes using Insomnia.VS Code
or any other editorNode.js
Insomnia
or PostmanPrettier
VS code extension to format the codeCreate a new folder and name it anything that you like and then open the folder in VS code and run the following code from the command prompt.
npm init -y
After running this command you will find a
package.json
if the folder.Run the following commands in the terminal to install the dependencies
npm i cors dotenv express mongoose
cors
: allows cross-origin api callsdotenv
: needed to access data from .env
filesexpress
: web application framework for node.jsmongoose
: It is needed to define the database schema and connecting to mongoDB
Now install the following development dependencies,
-D
is used to install the development dependencies.npm i -D nodemon
After installing the dependencies the
package.json
folder should look as follows.// package.json
{
"name": "mongodb-schema-populate-blog",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/mritunjaysaha/mongodb-schema-populate-blog.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/mritunjaysaha/mongodb-schema-populate-blog/issues"
},
"homepage": "https://github.com/mritunjaysaha/mongodb-schema-populate-blog#readme",
"dependencies": {
"dotenv": "^10.0.0",
"express": "^4.17.1",
"mongoose": "^5.13.3"
},
"devDependencies": {
"nodemon": "^2.0.12"
}
}
Now, create a
server.js
file and a .env
. The server.js
file will be the entry point of the server and the .env
file will contain the MONGO_URI
. We also have to make the following changes in the package.json
//package.json
{
"name": "mongodb-schema-populate-blog",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/mritunjaysaha/mongodb-schema-populate-blog.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/mritunjaysaha/mongodb-schema-populate-blog/issues"
},
"homepage": "https://github.com/mritunjaysaha/mongodb-schema-populate-blog#readme",
"dependencies": {
"dotenv": "^10.0.0",
"express": "^4.17.1",
"mongoose": "^5.13.3"
},
"devDependencies": {
"nodemon": "^2.0.12"
}
}
Now, create the following folders
config
: Inside the config
folder, create a file named db.js
. This file will contain the required code for connecting to the MongoDB
database.
controllers
: The controllers
folder will contain the files which will have the methods for the end points to communicate with the database.
models
: The models
folder, will contain the files which will define the MongoDB schema
routers
: The routers
folder will contain the files with the endpoints
.
At this stage the file structure should look as follows
.
├── config
│ └── db.js
├── controllers
│ └── user.js
├── models
│ ├── todo.js
│ └── user.js
├── node_modules
├── routes
│ └── user.js
├── .env
├── server.js
├── package-lock.json
└── package.json
"scripts": {
"start":"node server.js",
"dev":"nodemon server.js"
}
The
package.json
file should look as follows{
"name": "mern-todo",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js", //added
"dev": "nodemon server.js" //added
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"config": "^3.3.6",
"cors": "^2.8.5",
"dotenv": "^10.0.0",
"express": "^4.17.1",
"mongoose": "^5.13.2"
},
"devDependencies": {
"nodemon": "^2.0.12"
}
}
We will do the following to setup the server
express
express()
get
method for the endpoint http://localhost:8000
using app.get()
PORT
to 8000
for our server to runPORT
using app.listen()
.
├── config
│ └── db.js
├── controllers
│ └── user.js
├── models
│ ├── todo.js
│ └── user.js
├── node_modules
├── routes
│ └── user.js
├── .env
├── server.js <-- we are here
├── package-lock.json
└── package.json
The code will look as follows
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// server.js | |
const express = require("express"); | |
const app = express(); | |
// initialize middleware | |
app.use(express.json({ extended: false })); | |
app.get("/", (req, res) => res.send("Server up and running")); | |
// setting up port | |
const PORT = process.env.PORT || 8000; | |
app.listen(PORT, () => { | |
console.log(`server is running on http://localhost:${PORT}`); | |
}); |
And start the server using
nodemon
using the following code. Make sure you are running the following command from the project directory.npm run dev
If the server has started successfully then it should show the following message in the terminal
[nodemon] 2.0.11
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node server.js`
server is running on http://localhost:8000
You can also open
http://localhost:8000
on your browser.To connect to the database we will need the link for the
mongoDB
collection.allow access from anywhere
. Then Add IP address

//.env
MONGO_URI = mongodb+srv://<username>:<password>@cluster0.owmij.mongodb.net
Replace the
<username>
and <password>
with your database username and password which you will set in step 9..
├── config
│ └── db.js <-- we are here
├── controllers
│ └── user.js
├── models
│ ├── todo.js
│ └── user.js
├── node_modules
├── routes
│ └── user.js
├── .env
├── server.js
├── package-lock.json
└── package.json
Now, open the
db.js
file which is in the config
folder and add the following changes.mongoose
MONGO_URI
from .env
connectDB
methof for connecting to the databaseconnectDB
method to be called in server.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// config/db.js | |
const mongoose = require("mongoose"); | |
const db = process.env.MONGO_URI; | |
const connectDB = async () => { | |
try { | |
await mongoose.connect(db, { | |
useNewUrlParser: true, | |
useUnifiedTopology: true, | |
}); | |
console.log("MongoDB is connected"); | |
} catch (err) { | |
console.error(err.message); | |
process.exit(1); | |
} | |
}; | |
module.exports = connectDB; |
Add the following changes in the
server.js
file.dotenv
connectDB
method from config/db.js
connectDB
method.Let us make the the following changes in
server.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// server.js | |
require("dotenv").config(); //added | |
const express = require("express"); | |
const connectDB = require("./config/db"); //added | |
const app = express(); | |
// connect database | |
connectDB();//added | |
// initialize middleware | |
app.use(express.json({ extended: false })); | |
app.get("/", (req, res) => res.send("Server up and running")); | |
// setting up port | |
const PORT = process.env.PORT || 8000; | |
app.listen(PORT, () => { | |
console.log(`server is running on http://localhost:${PORT}`); | |
}); |
Save the changes it will restart the server or use the command
npm run dev
. The terminal should show a message of MongoDB is connected
which we have added in the db.js
under the try block.Create a
todo.js
file in the models folder. We will define the database schema in this file..
├── config
│ └── db.js
├── controllers
│ └── user.js
├── models
│ ├── todo.js <-- we are here
│ └── user.js
├── node_modules
├── routes
│ └── user.js
├── .env
├── server.js
├── package-lock.json
└── package.json
mongoose
Schema
called TodoSchema
title
and description
title
will be String
and it is a mandatory fielddescription
will be String
and it is not a mandatory fieldThe code will look as follows
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// models/todo.js | |
const mongoose = require("mongoose"); | |
const { Schema } = mongoose; | |
const TodoSchema = new Schema({ | |
title: { type: String, required: true }, | |
description: { type: String }, | |
}); | |
const Todo = mongoose.model("Todo", TodoSchema); | |
module.exports = Todo; |
Create a schema for the user using the above steps.
After making the changes, the user model will look something like this
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// models/user.js | |
const mongoose = require("mongoose"); | |
const { Schema } = mongoose; | |
const UserSchema = new Schema({ | |
name: { | |
type: String, | |
required: true, | |
}, | |
todo: [{ type: Schema.Types.ObjectId, ref: "Todo" }], | |
}); | |
const User = mongoose.model("User", UserSchema); | |
module.exports = User; |
.
├── config
│ └── db.js
├── controllers
│ └── user.js <-- we are here
├── models
│ └── todo.js
├── node_modules
├── routes
│ └── user.js
├── .env
├── server.js
├── package-lock.json
└── package.json
Todo
and User
schemascreateUser
method will create a new usercreateTodo
method will do the following
- create a new todo
- save the todo
- use the
userId
to find the user - update the
todo
array with theObjectId
of the new todo
getUser
to get the user details. The output of this method we can see that todo
consists of some random value which is the ObjectId
of the todo
that the user has created. We cannot figure out what the todo contains.
{
"todo": ["61023642610b8d4ce4f56f81", "6102365b610b8d4ce4f56f84"],
"_id": "6102361f610b8d4ce4f56f7f",
"name": "test-user",
"__v": 0
}
getAllTodo
method we will use the userId
to find the user and then use the populate
method to reference the todo
with the ObjectId
from the Todo
table. The exec
method is used to check for errors and return the populated data.
{
"todo": [
{
"_id": "61023642610b8d4ce4f56f81",
"title": "test-title-1",
"description": "test-description-1",
"__v": 0
},
{
"_id": "6102365b610b8d4ce4f56f84",
"title": "test-title-2",
"description": "test-description-2",
"__v": 0
}
],
"_id": "6102361f610b8d4ce4f56f7f",
"name": "test-user",
"__v": 0
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// controllers/user.js | |
const User = require("../models/user"); | |
const Todo = require("../models/todo"); | |
exports.createUser = (req, res) => { | |
User.create(req.body) | |
.then((data) => res.json(data)) | |
.catch((err) => | |
res | |
.status(400) | |
.json({ message: "Failed to create user", error: err.message }) | |
); | |
}; | |
exports.createTodo = (req, res) => { | |
// extracting userId | |
const { userId } = req.params; | |
// creating new todo | |
const newTodo = new Todo(req.body); | |
// saving newTodo | |
newTodo.save(); | |
User.findByIdAndUpdate( | |
userId, | |
{ $push: { todo: newTodo._id } }, | |
{ new: true, upsert: true }, | |
(err, user) => { | |
if (err) { | |
res.status(400).json({ message: "error" }); | |
} | |
res.json(user); | |
} | |
); | |
}; | |
exports.getUser = (req, res) => { | |
const { userId } = req.params; | |
User.findById(userId) | |
.then((data) => res.json(data)) | |
.catch((err) => | |
res | |
.status(400) | |
.json({ message: "User not found", error: err.message }) | |
); | |
}; | |
exports.getAllTodo = (req, res) => { | |
// extracting userId | |
const { userId } = req.params; | |
User.findByIdAndUpdate(userId) | |
.populate("todo") | |
.exec((err, user) => { | |
if (err) { | |
res.status(400).json({ | |
message: "Failed to populate", | |
error: err.message, | |
}); | |
} | |
res.json(user); | |
}); | |
}; |
.
├── config
│ └── db.js
├── controllers
│ └── user.js
├── models
│ └── todo.js
├── node_modules
├── routes
│ └── user.js <-- we are here
├── .env
├── server.js
├── package-lock.json
└── package.json
We will define the end points to
create
users and todo and to read
them.express
controllers
router
POST
method to create
a userPOST
method to create
a todo and save it in the userGET
method to read
user dataGET
method to read
user data and todo dataAfter making the above changes the code will look something like this
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// routes/user.js | |
const express = require("express"); | |
const router = express.Router(); | |
const { | |
createUser, | |
createTodo, | |
getUser, | |
getAllTodo, | |
} = require("../controllers/user"); | |
/** | |
* @route POST /api/create | |
* @description create user | |
*/ | |
router.post("/", createUser); | |
/** | |
* @route POST /api/todo/:userId | |
* @description add todo ObjectId to Users todo array | |
*/ | |
router.post("/todo/:userId", createTodo); | |
/** | |
* @route GET /api/:userId | |
* @description get user details | |
*/ | |
router.get("/:userId", getUser); | |
/** | |
* @route GET /api/get-all/:userId | |
* @description populate the todo schema | |
*/ | |
router.get("/todo/:userId", getAllTodo); | |
module.exports = router; |
.
├── config
│ └── db.js
├── controllers
│ └── todo.js
├── models
│ └── todo.js
├── node_modules
├── routes
│ └── todo.js
├── .env
├── server.js <-- we are here
├── package-lock.json
└── package.json
The final part of completing the backend is to add the endpoints to the
server.js
file.routes/todo.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// server.js | |
require("dotenv").config(); | |
const express = require("express"); | |
const connectDB = require("./config/db"); | |
const user = require("./routes/user"); | |
// initialize app | |
const app = express(); | |
// import routes | |
// connect to database | |
connectDB(); | |
// initialize middleware | |
app.use(express.json({ extended: false })); | |
// basic test route to check whether the server is active | |
app.get("/", (req, res) => res.send("Server is active")); | |
// api routes | |
app.use("/api/user", user); | |
const PORT = process.env.PORT || 8000; | |
app.listen(PORT, () => { | |
console.log(`Server is running on http://localhost:${PORT}`); | |
}); |
We will send a
POST
request to http://localhost:8000/api/user

We will send a
POST
request to http://localhost:8000/api/user/todo/:userId
copy the
_id
from the response of the create a user request


We will send a
GET
request to http://localhost:8000/api/user/:userId

We will send a
POST
request to http://localhost:8000/api/user/todo/:userId

You can check the code in GitHub
23