EigenFool
EigenFool

Reputation: 533

What is the proper way to do seed mongoDB in NestJS, using mongoose and taking advantage of my already defined schmas

We are using NestJS with mongoose and want to seed mongoDB. Wondering what is the proper way to seed the database, and use the db schemas already defined to ensure the data seeded is valid and properly maintained.

Seeding at the module level (just before the definition of the Module) feels hacky and ends in threadpool being destroyed, and therefore all following mongo operations fail

Upvotes: 16

Views: 15431

Answers (3)

gxmad
gxmad

Reputation: 2220

For my case, I needed to insert seed during the tests, the best I could find is to create a seed service, imported and used only during tests.

Here is my base class using the schema model, all is needed is to extend and pass the model.

// # base.seed.service.ts

import { Model, Document } from 'mongoose';

import { forceArray, toJson } from 'src/utils/code';

export abstract class BaseSeedService<D extends Document> {
  constructor(protected entityModel: Model<D>) {}

  async insert<T = any>(data: T | T[]): Promise<any[]> {
    const docs = await this.entityModel.insertMany(forceArray(data));
    return toJson(docs);
  }
}


// # utils
const toJson = (arg: any) => JSON.parse(JSON.stringify(arg));
function forceArray<T = any>(instance: T | T[]): T[] {
   if (instance instanceof Array) return instance;
   return [instance];
}


// # dummy.seed.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';

import { DummyDocument } from './dummy.schema';

@Injectable()
export class DummySeedService extends BaseSeedService<DummyDocument> {
  constructor(
    @InjectModel(Dummy.name)
    protected model: Model<DummyDocument>,
  ) {
    super(model);
  }
}

Then inside the tests

describe('Dymmy Seeds', () => {
  
  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [DummySeedService],
      imports: [
        MongooseModule.forRoot(__connect_to_your_mongodb_test_db__),
        MongooseModule.forFeature([
          {
            name: Dummy.name,
            schema: DummySchema,
          },
        ]),
      ],
    }).compile();

    const seeder = module.get<DummySeedService>(DummySeedService);
    const initData = [__seed_data_here__];
    const entities: Dummy[] = await seeder.insert(initData);
    expect(entities.length > 0).toBeTruthy();
  });
});

Upvotes: 1

Aditya Fawzan
Aditya Fawzan

Reputation: 51

actually you can do it easily with onModuleInit(), here i'm using Mongoose ORM. This all done with zero dependencies, hope it helps

import { Injectable, OnModuleInit } from '@nestjs/common';
import { UserRepository } from './repositories/user.repository';

@Injectable()
export class UserService implements OnModuleInit {
  constructor(private readonly userRepository: UserRepository) {}

  // onModuleInit() is executed before the app bootstraped
  async onModuleInit() {
    try {
      const res = await this.userRepository.findAll(); // this method returns user data exist in database (if any)
      // checks if any user data exist
      if (res['data'] == 0) {
        const newUser = {
          name: 'yourname',
          email: '[email protected]',
          username: 'yourusername',
        };
        const user = await this.userRepository.create(newUser); // this method creates new user in database
        console.log(user);
      }
    } catch (error) {
      throw error;
    }
  }

  // your other methods
}

Upvotes: 5

Guilherme Reis
Guilherme Reis

Reputation: 326

I've done using the nestjs-command library like that.

1. Install the library:

https://www.npmjs.com/package/nestjs-command

2. Then I've created a command to seed my userService like:

src/modules/user/seeds/user.seed.ts

import { Command, Positional } from 'nestjs-command';
import { Injectable } from '@nestjs/common';

import { UserService } from '../../../shared/services/user.service';

@Injectable()
export class UserSeed {
constructor(
    private readonly userService: UserService,
) { }

@Command({ command: 'create:user', describe: 'create a user', autoExit: true })
async create() {
    const user = await this.userService.create({
        firstName: 'First name',
        lastName: 'Last name',
        mobile: 999999999,
        email: '[email protected]',
        password: 'foo_b@r',
    });
    console.log(user);
}
}

3. Add that seed command into your module. I've created a SeedsModule in a shared folder to add more seeds in future

src/shared/seeds.module.ts

import { Module } from '@nestjs/common';
import { CommandModule } from 'nestjs-command';

import { UserSeed } from '../modules/user/seeds/user.seed';
import { SharedModule } from './shared.module';

@Module({
    imports: [CommandModule, SharedModule],
    providers: [UserSeed],
    exports: [UserSeed],
})
export class SeedsModule {}

Btw I'm importing my userService into my SharedModule

4. Add the SeedsModule into your AppModule

On your AppModule usually at src/app.module.ts add the SeedsModule into imports

Final

If you followed the steps in the nestjs-command repo you should be able to run

npx nestjs-command create:user

That will bootstrap a new application and run that command and then seed to your mongo/mongoose

Hope that help others too.

Upvotes: 26

Related Questions