pre('save') and pre('remove') Hooks not triggering in nestjs.
I just started with nestjs. I've encountered an issue in which pre hooks are not working.
I followed official nestjs documentation for mongoose. but nothing works in my case
I want to update the product when review is created, deleted, and updated.
I want to inject productServiceinto hooks.
Below are the relevant snippets.
package.json
{
"name": "server",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/common": "^7.5.1",
"@nestjs/config": "^0.6.1",
"@nestjs/core": "^7.5.1",
"@nestjs/graphql": "^7.9.4",
"@nestjs/jwt": "^7.2.0",
"@nestjs/mongoose": "^7.2.4",
"@nestjs/passport": "^7.1.5",
"@nestjs/platform-express": "^7.5.1",
"@nestjs/serve-static": "^2.1.4",
"@types/bcrypt": "^3.0.0",
"apollo-server-express": "^2.19.1",
"bcrypt": "^5.0.0",
"class-transformer": "^0.3.2",
"class-validator": "^0.13.1",
"cloudinary": "^1.23.0",
"cookie-parser": "^1.4.5",
"graphql": "^15.4.0",
"graphql-tools": "^7.0.2",
"graphql-upload": "^11.0.0",
"mongoose": "^5.11.19",
"passport": "^0.4.1",
"passport-jwt": "^4.0.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^6.6.3",
"slugify": "^1.4.6",
"stripe": "^8.132.0"
},
"devDependencies": {
"@nestjs/cli": "^7.5.1",
"@nestjs/schematics": "^7.1.3",
"@nestjs/testing": "^7.5.1",
"@types/cookie-parser": "^1.4.2",
"@types/express": "^4.17.8",
"@types/jest": "^26.0.15",
"@types/node": "^14.14.6",
"@types/passport-jwt": "^3.0.3",
"@types/supertest": "^2.0.10",
"@typescript-eslint/eslint-plugin": "^4.6.1",
"@typescript-eslint/parser": "^4.6.1",
"eslint": "^7.12.1",
"eslint-config-prettier": "7.0.0",
"eslint-plugin-prettier": "^3.1.4",
"jest": "^26.6.3",
"prettier": "^2.1.2",
"supertest": "^6.0.0",
"ts-jest": "^26.4.3",
"ts-loader": "^8.0.8",
"ts-node": "^9.0.0",
"tsconfig-paths": "^3.9.0",
"typescript": "^4.2.3"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
Review Module
@Module({
imports: [
forwardRef(() => ProductModule),
MongooseModule.forFeatureAsync([{
name: Review.name,
useFactory: (
) => {
const schema = ReviewSchema
schema.pre<ReviewDoc>('save', function (next) {
console.log("INSIDE SAVE")
next()
})
schema.pre<ReviewDoc>('remove', function (next) {
console.log("INSIDE REMOVE")
next()
})
return schema
}
}]),
MongooseModule.forFeature([{ name: Product.name, schema: ProductSchema }]),
MongooseModule.forFeature([{ name: Member.name, schema: MemberSchema }]),
MongooseModule.forFeature([{ name: Review.name, schema: ReviewSchema }])
],
providers: [ReviewService, ReviewResolver],
exports: [ReviewService]
})
export class ReviewModule { }
Review Entity
import { SchemaFactory, Schema, Prop } from "@nestjs/mongoose";
import * as mongoose from 'mongoose'
import { Member } from "../member/member.entity";
import { IReviewMember } from "./review.type";
export type ReviewDoc = Review & mongoose.Document
@Schema({ _id: false })
class ReviewMember {
@Prop({ type: mongoose.Schema.Types.ObjectId, ref: Member.name, required: true })
id: string;
@Prop({ required: true })
firstName: string;
@Prop({ required: true })
lastName: string;
@Prop({ type: String })
avatar?: string;
}
export const ReviewMemberSchema = SchemaFactory.createForClass(ReviewMember)
@Schema({
timestamps: true,
toJSON: {
transform: (doc, ret) => {
ret.id = doc._id;
delete ret._id
delete ret.__v
}
}
})
export class Review {
@Prop({ type: ReviewMemberSchema, required: true })
member: IReviewMember;
@Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'products', required: true })
productId: string;
@Prop()
productSlug: string;
@Prop()
comment: string;
@Prop({
set: function (rating: number) {
this._prevRating = this.rating;
return rating;
}
})
rating: number;
}
const ReviewSchema = SchemaFactory.createForClass(Review)
export { ReviewSchema }
Review Service
async createReview({ comment, member: user, productId, rating }: ICreateReview) {
const member = await this.members.findById(user.id)
if (!member) {
throw new NotFoundException(`no member found with member id "${productId}"`)
}
const product = await this.products.findById(productId)
if (!product) {
throw new NotFoundException(`no product found with product id "${productId}"`)
}
const review = await this.reviews.findOne({ "member.id": member.id, productId: product._id })
if (review) {
throw new ConflictException(`review already exist`)
}
// should trigger pre save but not working
const newReview = await this.reviews.create(
{
productId: product._id,
productSlug: product.slug,
member: {
firstName: member.firstName,
lastName: member.lastName,
avatar: member.avatar,
id: member._id
},
rating,
comment,
}
)
if (product.numOfReviews < 3) {
const { comment, member, rating, _id } = newReview
product.reviews.push({ comment, member, rating, id: _id })
await product.save()
}
return newReview;
}```