Thimma
Thimma

Reputation: 1492

NestJS with MongoDB and NestJsxAutomapper resulting in error 'cannot read property plugin of undefined'

I am working on an API with NestJS, and because I have DTO's I am using an AutoMapper (made by @nartc and/or nestjsx), I have tried to make my example as small as I could with the Foo example, because I use multiple files.

This is my module:

// foo.module.ts
import { Module } from "@nestjs/common";
import { MongooseModule } from "@nestjs/mongoose";

import { Foo, FooSchema } from "./foo.entity.ts";
import { FooController } from "./foo.controller.ts";
import { FooService } from "./foo.service.ts";
import { FooProfile } from "./foo.profile.ts";

@Module({
  imports: [
    MongooseModule.forFeature([
      {
        name: Foo.name,
        schema: FooSchema,
        collection: "foos",
      }
    ])
    // FooProfile <-- if I uncomment this, the program will give the error (shown at the bottom of this question)
  ],
  controllers: [FooController],
  providers: [FooProivder],
})
export class FooModule {}

This is my entity:

// foo.entity.ts
import { Schema, SchemaFactory, Prop } from "@nestjs/mongoose";
import { Document } from "mongoose";

@Schema()
export class Foo extends Document { // if I remove the `extends Document` it works just fine
  @Prop({ required: true })
  name: string;
  @Prop()
  age: number
}

export const FooSchema = SchemaFactory.createForClass(Foo);

This is my DTO:

// foo.dto.ts
export class FooDTO {
  name: string;
}

This is my controller:

// foo.controller.ts
import { Controller, Get } from "@nestjs/common";
import { InjectMapper, AutoMapper } from "nestjsx-automapper";

import { Foo } from "./foo.entity";
import { FooService } from "./foo.service";
import { FooDTO } from "./dto/foo.dto";

@Controller("foos")
export class FooController {
  constructor(
    private readonly fooService: FooService
    @InjectMapper() private readonly mapper: AutoMapper
  ) {}

  @Get()
  async findAll() {
    const foos = await this.fooService.findAll();
    const mappedFoos = this.mapper.mapArray(foos, Foo, FooDTO);
    // ^^ this throws an error of the profile being undefined (duh)
    return mappedFoos;
  }
}

This is my profile:

// foo.profile.ts
import { Profile, ProfileBase, InjectMapper, AutoMapper } from "nestjsx-automapper";

import { Foo } from "./foo.entity";
import { FooDTO } from "./foo.dto";

@Profile()
export class FooProfile extends ProfileBase {
  constructor(@InjectMapper() private readonly mapper: AutoMapper) {
    // I've read somewhere that the `@InjectMapper() private readonly` part isn't needed,
    // but if I exclude that, it doesn't get the mapper instance. (mapper will be undefined)
    super();
    this.mapper.createMap(Foo, FooDTO);
  }
}

If I uncomment the line I highlighted in the module, it will result in the following error..

[Nest] 11360   - 2020-08-18 15:53:06   [ExceptionHandler] Cannot read property 'plugin' of undefined +1ms
TypeError: Cannot read property 'plugin' of undefined
    at Foo.Document.$__setSchema ($MYPATH\node_modules\mongoose\lib\document.js:2883:10)
    at new Document ($MYPATH\node_modules\mongoose\lib\document.js:82:10)
    at new Foo($MYPATH\dist\foo\foo.entity.js:15:17)

I have also referred to this answer on stackoverflow, but that doesn't work for me either. I have also combined that with the documentation, but with no luck.. How would I get the AutoMapper to register my profiles?

Update

The error seems to originate from the foo entity, if I remove the extends Document and the Schema(), Prop({ ... }) from the class it works fine, it seems like I have to inject mongoose or something?

Upvotes: 4

Views: 4153

Answers (2)

Sorin Veștemean
Sorin Veștemean

Reputation: 1920

What worked for me.

1. Updated all the absolute paths for models, schemas, entities (is easy if you search for from '/src in your projects, and update all the routes to relative paths)

from: import { User } from 'src/app/auth/models/user/user.entity';

to: import { User } from './../../auth/models/user/user.entity';

2. mongoose imports:

from: import mongoose from 'mongoose';

to: import * as mongoose from 'mongoose';

3. Remove validation pipe if you don't use it. For some reason (I think i don't use them on the controller, I didn't investigate, I've removed from one controller the Validation Pipe) so If you have this try it:

from:

@Controller('someroute')
@UsePipes(new ValidationPipe())
export class SomeController {}

to:

@Controller('someroute')
export class SomeController {}

I hope my solution worked for you ^_^

Upvotes: 1

Chau Tran
Chau Tran

Reputation: 5098

In your module, just import the path to the profile like below:

import 'relative/path/to/foo.profile';

By importing the path to file, TypeScript will include the file in the bundle and then the @Profile() decorator will be executed. When @Profile() is executed, AutoMapperModule keeps track of all the Profiles then when it's turn for NestJS to initialize AutoMapperModule (with withMapper() method), AutoMapperModule will automatically add the Profiles to the Mapper instance.

With that said, in your FooProfile's constructor, you'll get AutoMapper instance that this profile will be added to

@Profile()
export class FooProfile extends ProfileBase {
   // this is the correct syntax. You would only need private/public access modifier
   // if you're not going to use this.mapper outside of the constructor
   // You DON'T need @InjectMapper() because that's Dependency Injection of NestJS. 
   // Profile isn't a part of NestJS's DI
   constructor(mapper: AutoMapper) {

   }
}

The above answer will solve your problems with AutoMapper. As far as your Mongoose problem, I would need a sample repro to tell for sure. And also, visit our Discord for this kind of question.

Upvotes: 1

Related Questions