DEV Community

Munisekhar Udavalapati
Munisekhar Udavalapati

Posted on

Mongoose with NestJS and MongoDB: A Complete Guide || By Munisekhar Udavalapati

Introduction

Mongoose is an Object Data Modeling (ODM) library for MongoDB and Node.js. It provides schema validation, middleware, and built-in query-building methods. In this guide, we will integrate Mongoose with NestJS to create a structured and scalable backend application using MongoDB.

Prerequisites

  • Basic knowledge of TypeScript and NestJS
  • Installed Node.js and NestJS CLI
  • A running instance of MongoDB (local or cloud-based)

Installing Dependencies

To use Mongoose with NestJS, install the required packages:

npm install @nestjs/mongoose mongoose
Enter fullscreen mode Exit fullscreen mode

For TypeScript types:

npm install -D @types/mongoose
Enter fullscreen mode Exit fullscreen mode

Setting Up Mongoose in NestJS

NestJS provides MongooseModule to manage MongoDB connections. Add it to the root module:

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';

@Module({
  imports: [MongooseModule.forRoot('mongodb://localhost:27017/nestjs-app', {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  })],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Understanding Mongoose Concepts

1. Schema

Defines the structure of documents in a collection.

2. Model

A wrapper for the schema that allows interacting with the database.

3. Document

An instance of a model that represents a single record.

4. Middleware

Functions that execute before or after certain actions (e.g., saving a document).

5. Virtuals

Computed properties that are not stored in the database.

6. Hooks

Functions that run before or after certain Mongoose operations (e.g., pre-save, post-save).

Creating a Mongoose Schema and Model

Define a schema using decorators with @nestjs/mongoose.

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

@Schema({ timestamps: true })
export class User extends Document {
  @Prop({ required: true })
  name: string;

  @Prop({ unique: true, required: true })
  email: string;

  @Prop({ default: Date.now })
  createdAt: Date;
}

export const UserSchema = SchemaFactory.createForClass(User);
Enter fullscreen mode Exit fullscreen mode

Adding Middleware (Hooks)

Middleware functions allow executing logic before or after certain events like saving a document.

UserSchema.pre('save', function(next) {
  console.log('User is being saved:', this);
  next();
});
Enter fullscreen mode Exit fullscreen mode

Defining Virtuals

Virtuals allow creating computed properties that do not get stored in MongoDB.

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

Registering the Schema in the Module

To use the schema, register it in a module:

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { User, UserSchema } from './user.schema';
import { UserService } from './user.service';
import { UserController } from './user.controller';

@Module({
  imports: [MongooseModule.forFeature([{ name: User.name, schema: UserSchema }])],
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}
Enter fullscreen mode Exit fullscreen mode

Creating a Service for Database Operations

import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { User } from './user.schema';

@Injectable()
export class UserService {
  constructor(@InjectModel(User.name) private userModel: Model<User>) {}

  async createUser(name: string, email: string): Promise<User> {
    const newUser = new this.userModel({ name, email });
    return newUser.save();
  }
}
Enter fullscreen mode Exit fullscreen mode

Querying with Mongoose

Find all users:

async getUsers(): Promise<User[]> {
  return this.userModel.find().exec();
}
Enter fullscreen mode Exit fullscreen mode

Find by ID:

async getUserById(id: string): Promise<User> {
  return this.userModel.findById(id).exec();
}
Enter fullscreen mode Exit fullscreen mode

Select Specific Fields:

async getUserByEmail(email: string): Promise<User> {
  return this.userModel.findOne({ email }).select('name email').exec();
}
Enter fullscreen mode Exit fullscreen mode

Using Indexes for Performance Optimization

UserSchema.index({ email: 1 });
Enter fullscreen mode Exit fullscreen mode

Find with Filtering:

async getUsersByCreatedDate(date: Date): Promise<User[]> {
  return this.userModel.find({ createdAt: { $gte: date } }).exec();
}
Enter fullscreen mode Exit fullscreen mode

Sorting Results:

async getUsersSorted(): Promise<User[]> {
  return this.userModel.find().sort({ name: 1 }).exec();
}
Enter fullscreen mode Exit fullscreen mode

Pagination with Limit & Skip:

async getUsersPaginated(page: number, limit: number): Promise<User[]> {
  return this.userModel.find().skip((page - 1) * limit).limit(limit).exec();
}
Enter fullscreen mode Exit fullscreen mode

Transactions in MongoDB

To ensure atomic operations, use MongoDB transactions:

const session = await this.userModel.db.startSession();
session.startTransaction();
try {
  const user = new this.userModel({ name, email });
  await user.save({ session });
  await session.commitTransaction();
} catch (error) {
  await session.abortTransaction();
} finally {
  session.endSession();
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  • Use DTOs for validation and request handling
  • Enable indexes for better query performance
  • Use MongoDB Transactions for atomic operations
  • Implement middleware for logging and error handling
  • Keep schema definitions clean and structured
  • Use .select() to fetch only necessary fields for efficiency
  • Use .sort(), .skip(), and .limit() for better data retrieval management

Conclusion

This guide covers setting up Mongoose with NestJS, defining schemas, implementing CRUD operations, handling transactions, indexing, filtering, and best practices. With this foundation, you can build powerful and optimized applications using NestJS and MongoDB.

Top comments (0)