DEV Community

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

Posted on

1 1

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)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

πŸ‘‹ Kindness is contagious

Please leave a ❀️ or a friendly comment on this post if you found it helpful!

Okay