MongoDB with Mongoose ORM

Table of Contents

  1. Introduction to Mongoose
  2. What is an ORM (Object Relational Mapping)?
  3. Setting Up Mongoose
  4. Defining Mongoose Models
  5. CRUD Operations Using Mongoose
    • Create (insertOne, insertMany)
    • Read (findOne, find)
    • Update (updateOne, updateMany)
    • Delete (deleteOne, deleteMany)
  6. Mongoose Validation
  7. Mongoose Middleware
  8. Relationships in Mongoose (Population)
  9. Best Practices for Mongoose
  10. Conclusion

1. Introduction to Mongoose

Mongoose is a popular ODM (Object Data Mapping) library for MongoDB and Node.js. It provides a powerful schema-based solution to model your data, offering easy-to-use methods for querying, validating, and interacting with MongoDB documents. Mongoose simplifies working with MongoDB by providing schemas, models, and middleware to handle database operations.


2. What is an ORM (Object Relational Mapping)?

ORM stands for Object-Relational Mapping. It’s a technique for converting data between incompatible type systems in object-oriented programming languages, like JavaScript, and relational databases. In the case of Mongoose, it works as an ODM (Object Document Mapping), which is specifically designed for NoSQL databases like MongoDB.

  • MongoDB is a NoSQL database, which means it doesn’t store data in tables as traditional relational databases (RDBMS) do. Instead, it uses collections and documents to store data.
  • Mongoose acts as an intermediary layer between the MongoDB database and Node.js applications, allowing developers to interact with MongoDB through JavaScript objects, models, and schemas.

3. Setting Up Mongoose

To begin using Mongoose in your Node.js application, you first need to install it and set up a connection to MongoDB.

Installation

bashCopyEditnpm install mongoose dotenv

Setup .env File for Database Configuration

envCopyEditMONGO_URI=mongodb://127.0.0.1:27017/myapp

Establishing the Connection

jsCopyEditconst mongoose = require('mongoose');
require('dotenv').config();

mongoose.connect(process.env.MONGO_URI, {
  useNewUrlParser: true,
  useUnifiedTopology: true
}).then(() => {
  console.log('MongoDB Connected');
}).catch((err) => {
  console.error('Connection Error:', err);
});

This establishes a connection to MongoDB using the URI stored in the .env file.


4. Defining Mongoose Models

Mongoose uses schemas to define the structure of your documents. A schema is a blueprint for a MongoDB document, which allows you to define the fields, their types, default values, and validation rules.

Example: Defining a User Schema

jsCopyEditconst mongoose = require('mongoose');

// Create a user schema
const userSchema = new mongoose.Schema({
  name: {
    type: String,
    required: true
  },
  email: {
    type: String,
    required: true,
    unique: true
  },
  age: {
    type: Number,
    default: 18
  }
});

// Create a model based on the schema
const User = mongoose.model('User', userSchema);

In the example above, the User model is based on the userSchema, and it will allow you to interact with the users collection in MongoDB.


5. CRUD Operations Using Mongoose

Create Operation

insertOne

jsCopyEditconst newUser = new User({
  name: 'John Doe',
  email: '[email protected]',
  age: 25
});

newUser.save().then((user) => {
  console.log('User Created:', user);
}).catch((err) => {
  console.error('Error:', err);
});

insertMany

jsCopyEditUser.insertMany([
  { name: 'Jane Smith', email: '[email protected]', age: 30 },
  { name: 'Alice Johnson', email: '[email protected]', age: 22 }
]).then((users) => {
  console.log('Multiple Users Created:', users);
}).catch((err) => {
  console.error('Error:', err);
});

Read Operation

findOne

jsCopyEditUser.findOne({ email: '[email protected]' })
  .then((user) => {
    console.log('User Found:', user);
  })
  .catch((err) => {
    console.error('Error:', err);
  });

find

jsCopyEditUser.find({ age: { $gt: 20 } })
  .then((users) => {
    console.log('Users Found:', users);
  })
  .catch((err) => {
    console.error('Error:', err);
  });

Update Operation

updateOne

jsCopyEditUser.updateOne({ email: '[email protected]' }, { $set: { age: 26 } })
  .then(() => {
    console.log('User Updated');
  })
  .catch((err) => {
    console.error('Error:', err);
  });

updateMany

jsCopyEditUser.updateMany({ age: { $lt: 30 } }, { $inc: { age: 1 } })
  .then(() => {
    console.log('Multiple Users Updated');
  })
  .catch((err) => {
    console.error('Error:', err);
  });

Delete Operation

deleteOne

jsCopyEditUser.deleteOne({ email: '[email protected]' })
  .then(() => {
    console.log('User Deleted');
  })
  .catch((err) => {
    console.error('Error:', err);
  });

deleteMany

jsCopyEditUser.deleteMany({ age: { $lt: 25 } })
  .then(() => {
    console.log('Multiple Users Deleted');
  })
  .catch((err) => {
    console.error('Error:', err);
  });

6. Mongoose Validation

Mongoose provides powerful built-in validation methods, which can be applied to individual fields within your schema.

jsCopyEditconst userSchema = new mongoose.Schema({
  name: { type: String, required: true },
  email: { 
    type: String, 
    required: true, 
    unique: true, 
    match: /^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$/ 
  },
  age: { type: Number, min: 18, max: 100 }
});
  • required: Ensures the field is not empty.
  • unique: Ensures no two documents have the same value for this field.
  • match: Validates the field based on a regular expression.
  • min/max: Validates numeric fields to ensure values fall within the specified range.

7. Mongoose Middleware

Mongoose supports middleware (also called hooks), which allows you to add custom logic before or after certain actions like saving, deleting, or updating a document.

Example: Pre-save Middleware

jsCopyEdituserSchema.pre('save', function(next) {
  if (this.age < 18) {
    throw new Error('Age must be at least 18');
  }
  next();
});

8. Relationships in Mongoose (Population)

Mongoose allows you to populate referenced documents from other collections, making it easy to implement relationships like one-to-many or many-to-many.

Example: One-to-Many Relationship

jsCopyEditconst postSchema = new mongoose.Schema({
  title: String,
  content: String,
  author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
});

const Post = mongoose.model('Post', postSchema);

Post.find().populate('author').exec((err, posts) => {
  console.log(posts);
});

9. Best Practices for Mongoose

  • Schema Design: Design schemas to be as specific as possible, avoiding overly general models.
  • Indexes: Create indexes on fields you frequently query to optimize performance.
  • Error Handling: Always use try-catch or .catch() for error handling to handle MongoDB operation failures.
  • Validation: Use built-in Mongoose validation and custom validators for data integrity.
  • Middleware: Use Mongoose middleware for tasks like hashing passwords or updating timestamps.

10. Conclusion

Mongoose is an extremely powerful library for interacting with MongoDB in Node.js. It abstracts away many of the complexities of MongoDB and provides a user-friendly way to define schemas, perform CRUD operations, validate data, and manage relationships.