DEV Community

Cover image for Understanding Pre and Post Middleware in Mongoose with NestJS & TypeScript ๐Ÿš€
Abhinav
Abhinav

Posted on

Understanding Pre and Post Middleware in Mongoose with NestJS & TypeScript ๐Ÿš€

Mongoose provides powerful pre and post middleware hooks that allow you to execute logic before or after an operation (e.g., save, find, remove). These hooks are useful for data validation, transformation, logging, security, and more ๐Ÿ”.

During my recent endeavor to migrate the entire existing codebase to NEST, I discovered a set of tools that proved invaluable.

In this blog, we'll explore pre and post middleware in Mongoose using NestJS and TypeScript.


What is Middleware in Mongoose? ๐Ÿค”

Middleware (also called hooks) in Mongoose allows you to run functions before (pre) or after (post) certain operations on documents.

Supported Operations โš™๏ธ

Middleware can be applied to:

  • Document operations: save, remove, validate
  • Query operations: find, findOne, findOneAndUpdate, deleteOne
  • Aggregate operations: aggregate

Setting Up NestJS with Mongoose โšก

First, install Mongoose and its TypeScript types in a NestJS project:

npm install @nestjs/mongoose mongoose
npm install --save-dev @types/mongoose
Enter fullscreen mode Exit fullscreen mode

Next, let's create a User model with pre and post middleware.


Using pre Middleware in NestJS ๐Ÿ”’

Example 1: Hashing Password Before Saving

A common use case for pre middleware is hashing a password before saving it to the database.

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

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

  @Prop({ required: true })
  password: string;
}

const UserSchema = SchemaFactory.createForClass(User);

// Pre-save hook to hash password before saving
UserSchema.pre<User>('save', async function (next) {
  if (!this.isModified('password')) return next();

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

export { UserSchema };
Enter fullscreen mode Exit fullscreen mode

How it Works? ๐Ÿ”‘

  • The pre('save') middleware runs before saving a user.
  • It checks if the password field has been modified.
  • If modified, it hashes the password using bcrypt.
  • Finally, the next() function is called to continue the save process.

Using post Middleware in NestJS ๐Ÿ“ฃ

Example 2: Logging User Creation

We can use post middleware to log when a new user is created.

UserSchema.post<User>('save', function (doc) {
  console.log(`New user created: ${doc.username}`);
});
Enter fullscreen mode Exit fullscreen mode

How it Works? ๐Ÿ“

  • The post('save') middleware executes after a user is successfully saved.
  • It logs the created user's username.

Using pre Middleware for Query Operations ๐Ÿ”

Example 3: Auto-Populating a Reference Before Querying

Let's say a Post model has an author field referencing the User model. We can automatically populate the author field before fetching a post.

import { Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document, Types } from 'mongoose';
import { User } from './user.schema';

@Schema()
export class Post extends Document {
  @Prop({ required: true })
  title: string;

  @Prop({ type: Types.ObjectId, ref: 'User', required: true })
  author: User;
}

const PostSchema = SchemaFactory.createForClass(Post);

// Pre-find hook to auto-populate author
PostSchema.pre('find', function () {
  this.populate('author');
});

export { PostSchema };
Enter fullscreen mode Exit fullscreen mode

How it Works? ๐ŸŒ

  • pre('find') runs before executing a find() query.
  • this.populate('author') ensures that the author field is automatically populated.

Using post Middleware for Query Operations ๐Ÿ“Š

Example 4: Logging After a User is Found

We can use post middleware to log user retrievals.

UserSchema.post('findOne', function (doc) {
  if (doc) {
    console.log(`User found: ${doc.username}`);
  }
});
Enter fullscreen mode Exit fullscreen mode

How it Works? ๐Ÿ—‚๏ธ

  • post('findOne') runs after a document is found.
  • If a user is found, it logs their username.

Pre and Post Middleware for Deleting Documents ๐Ÿ—‘๏ธ

Example 5: Cleaning Up Related Data Before Deleting a User

If a user is deleted, we might want to remove their associated posts.

UserSchema.pre('deleteOne', { document: true, query: false }, async function (next) {
  const userId = this._id;
  await PostModel.deleteMany({ author: userId });
  console.log(`Deleted posts by user: ${userId}`);
  next();
});
Enter fullscreen mode Exit fullscreen mode

How it Works? ๐Ÿงน

  • pre('deleteOne') runs before deleting a user.
  • It removes all posts associated with the user.

Conclusion ๐ŸŽฏ

Mongoose middleware (pre and post) is a powerful tool for extending document behavior in a NestJS application. Some common use cases include:

โœ… Hashing passwords before saving users

โœ… Auto-populating referenced fields

โœ… Logging events after CRUD operations

โœ… Cleaning up related data before deleting documents

By leveraging these hooks, you can enhance data integrity, security, and performance in your NestJS applications.

Would you like to see middleware used in a real-world NestJS app? Let me know in the comments! ๐Ÿš€

Top comments (0)