Mongoose ODM with Express.js

When developing applications using Express.js with a MongoDB database, one of the most efficient and popular tools available is Mongoose, an Object Data Modeling (ODM) library for MongoDB and Node.js. Mongoose provides a straightforward, schema-based solution to model your application data, allowing you to interact with MongoDB using JavaScript objects. It simplifies data validation, casting, querying, and business logic management, making it an essential tool for developers working with MongoDB.

What is Mongoose?

Mongoose is an ODM (Object Data Modeling) library for MongoDB and Node.js. It provides a schema-based structure to the MongoDB collections and allows you to interact with MongoDB using models defined by schemas. Mongoose supports built-in data validation, type casting, query building, and more.

Why Use Mongoose with Express.js?

  • Schema Definitions: Mongoose allows you to define data schemas that your MongoDB collections should adhere to, providing a structure to the otherwise schema-less MongoDB.
  • Validation: Automatically validates data before saving it to the database.
  • Middleware: Provides pre and post hooks that help in performing actions before or after data operations.
  • Querying: Mongoose simplifies querying MongoDB with its built-in methods and chainable query functions.
  • Integration: Mongoose integrates seamlessly with Express.js, making it easy to manage application data.

Setting Up Mongoose with Express.js

Installing Dependencies

Before we can use Mongoose in our Express.js application, we need to install the necessary packages.

Installation:

				
					npm install express mongoose

				
			
  • express: The Express.js framework.
  • mongoose: The Mongoose ODM library for MongoDB.

Connecting to MongoDB with Mongoose

To connect your Express.js application to a MongoDB database using Mongoose, you need to establish a connection when the server starts.

File: app.js

				
					const express = require('express');
const mongoose = require('mongoose');
const app = express();

// Connect to MongoDB
mongoose.connect('mongodb://127.0.0.1:27017/mydatabase', {
  useNewUrlParser: true,
  useUnifiedTopology: true,
})
.then(() => console.log('MongoDB connected...'))
.catch(err => console.log(err));

app.use(express.json());

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

				
			

Explanation:

  • mongoose.connect(): Establishes a connection to the MongoDB database. The connection string mongodb://127.0.0.1:27017/mydatabase points to a MongoDB instance running on localhost with a database named mydatabase.
  • useNewUrlParser and useUnifiedTopology: These options ensure that the latest MongoDB driver connection mechanisms are used.

Output:

  • When the server starts, if the connection is successful, you’ll see MongoDB connected... in the console.

Defining Schemas and Models

What are Schemas in Mongoose?

A schema in Mongoose defines the structure of documents within a MongoDB collection. It determines the shape of the documents by specifying the fields, their data types, default values, and validation requirements.

Creating a Schema

To define a schema, you use the mongoose.Schema class, which allows you to specify the fields and their types.

File: models/User.js

				
					const mongoose = require('mongoose');

const UserSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true,
  },
  email: {
    type: String,
    required: true,
    unique: true,
  },
  age: {
    type: Number,
    min: 0,
  },
  createdAt: {
    type: Date,
    default: Date.now,
  },
});

module.exports = mongoose.model('User', UserSchema);

				
			

Explanation:

  • UserSchema: Defines a schema for the User collection.
  • name: A required string field.
  • email: A required and unique string field.
  • age: A number field with a minimum value of 0.
  • createdAt: A date field with a default value set to the current date and time.

Creating a Model

A model in Mongoose is a wrapper for the schema and provides an interface to interact with the MongoDB collection.

File: models/User.js (continued)

				
					module.exports = mongoose.model('User', UserSchema);

				
			

Explanation:

  • mongoose.model(‘User’, UserSchema): Creates a model named User based on the UserSchema. The User model represents the users collection in MongoDB.

Performing CRUD Operations

Create Operation

To insert a new document into the MongoDB collection, you can use the save() method provided by the Mongoose model.

File: app.js (continued)

				
					const User = require('./models/User');

app.post('/users', async (req, res) => {
  try {
    const user = new User(req.body);
    await user.save();
    res.status(201).json(user);
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

				
			

Explanation:

  • new User(req.body): Creates a new instance of the User model with the data from the request body.
  • user.save(): Saves the new user to the database.

Output:

  • When a POST request is sent to /users with user data (e.g., { name: 'John', email: 'john@example.com', age: 25 }), a new document is inserted into the users collection, and the created user is returned in the response.

Read Operation

To retrieve documents from the MongoDB collection, you can use the find() and findOne() methods.

File: app.js (continued)

				
					app.get('/users', async (req, res) => {
  try {
    const users = await User.find();
    res.json(users);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.get('/users/:id', async (req, res) => {
  try {
    const user = await User.findById(req.params.id);
    if (user) {
      res.json(user);
    } else {
      res.status(404).json({ error: 'User not found' });
    }
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

				
			

Explanation:

  • User.find(): Retrieves all documents in the users collection.
  • User.findById(req.params.id): Retrieves a single document by its _id field.

Output:

  • A GET request to /users returns all users in the collection.
  • A GET request to /users/:id returns the user with the specified ID or a 404 error if not found.

Update Operation

To update an existing document, you can use the findByIdAndUpdate() method.

File: app.js (continued)

				
					app.put('/users/:id', async (req, res) => {
  try {
    const user = await User.findByIdAndUpdate(req.params.id, req.body, {
      new: true,
      runValidators: true,
    });
    if (user) {
      res.json(user);
    } else {
      res.status(404).json({ error: 'User not found' });
    }
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

				
			

Explanation:

  • User.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true }): Finds a user by ID and updates it with the new data provided in the request body. The new: true option ensures that the updated document is returned, and runValidators: true applies schema validation during the update.

Output:

  • When a PUT request is sent to /users/:id, the specified user document is updated, and the updated document is returned.

Delete Operation

To delete a document from the collection, use the findByIdAndDelete() method.

File: app.js (continued)

				
					app.delete('/users/:id', async (req, res) => {
  try {
    const user = await User.findByIdAndDelete(req.params.id);
    if (user) {
      res.json({ message: 'User deleted' });
    } else {
      res.status(404).json({ error: 'User not found' });
    }
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

				
			

Explanation:

  • User.findByIdAndDelete(req.params.id): Finds a user by ID and deletes it from the collection.

Output:

  • A DELETE request to /users/:id deletes the specified user document and returns a confirmation message.

Mongoose Schema Features

Data Validation

Mongoose allows you to enforce data validation rules directly in the schema definition.

Example: Adding validation to the User schema.

File: models/User.js (updated)

				
					const UserSchema = new mongoose.Schema({
  name: {
    type: String,
    required: [true, 'Name is required'],
    minlength: [3, 'Name must be at least 3 characters long'],
  },
  email: {
    type: String,
    required: [true, 'Email is required'],
    unique: true,
    match: [/.+\@.+\..+/, 'Please enter a valid email address'],
  },
  age: {
    type: Number,
    min: [0, 'Age cannot be negative'],
    max: [120, 'Age cannot exceed 120 years'],
  },
});

				
			

Explanation:

  • required: [true, ‘Name is required’]: Ensures the name field is provided.
  • minlength: [3, ‘Name must be at least 3 characters long’]: Sets a minimum length for the name.
  • match: [/.+@.+..+/, ‘Please enter a valid email address’]: Validates the email format.

Middleware (Hooks)

Mongoose supports middleware functions (also known as hooks) that can be executed before or after certain operations, like save, remove, or update.

Example: Adding a pre-save hook to hash a user’s password.

File: models/User.js (updated)

				
					const bcrypt = require('bcrypt');

UserSchema.pre('save', async function (next) {
  if (!this.isModified('password')) return next();

  try {
    const salt = await bcrypt.genSalt(10);
    this.password = await bcrypt.hash(this.password, salt);
    next();
  } catch (err) {
    next(err);
  }
});

				
			

Explanation:

  • pre(‘save’, async function (next)): A pre-save hook that runs before a document is saved. It hashes the user’s password before saving it to the database.

Virtuals

Virtuals are document properties that you can get and set but do not get persisted to MongoDB.

Example: Creating a virtual field for the user’s full name.

File: models/User.js (updated)

				
					UserSchema.virtual('fullName').get(function () {
  return `${this.firstName} ${this.lastName}`;
});

				
			

Explanation:

  • virtual(‘fullName’).get(function () {…}): Defines a virtual field fullName that concatenates firstName and lastName.

Usage:

				
					const user = await User.findById(id);
console.log(user.fullName); // Outputs: 'John Doe'

				
			

Advanced Querying with Mongoose

Querying with Mongoose

Mongoose provides a powerful querying API that allows you to build complex queries.

Example: Finding users who are older than 18 and sorting them by age.

File: app.js (continued)

				
					app.get('/users/adults', async (req, res) => {
  try {
    const users = await User.find({ age: { $gt: 18 } }).sort('age');
    res.json(users);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

				
			

Explanation:

  • find({ age: { $gt: 18 } }): Finds users whose age is greater than 18.
  • sort(‘age’): Sorts the users by age in ascending order.

Output:

  • A GET request to /users/adults returns a list of users older than 18, sorted by age.

Aggregation Framework

Mongoose supports MongoDB’s aggregation framework, which allows for performing advanced data processing.

Example: Aggregating users by age group.

File: app.js (continued)

				
					app.get('/users/age-groups', async (req, res) => {
  try {
    const groups = await User.aggregate([
      { $match: { age: { $exists: true } } },
      { $group: { _id: { $floor: { $divide: ['$age', 10] } }, count: { $sum: 1 } } },
      { $sort: { _id: 1 } },
    ]);
    res.json(groups);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

				
			

Explanation:

  • aggregate([…]): Runs an aggregation pipeline on the users collection.
  • $match: Filters documents where the age field exists.
  • $group: Groups users into age groups by dividing their age by 10.
  • $sort: Sorts the results by age group.

Output:

  • A GET request to /users/age-groups returns a list of age groups and the count of users in each group.

In this chapter, we explored how to use Mongoose ODM with Express.js to interact with a MongoDB database. By mastering these concepts, you'll be able to efficiently manage your MongoDB database within an Express.js application, ensuring that your data is well-structured, validated, and easily accessible. Happy coding !❤️

Table of Contents

Contact here

Copyright © 2025 Diginode

Made with ❤️ in India