Mongo

Nest supports two methods for integrating with the MongoDB database. You can either use the built-in TypeORM module described here, which has a connector for MongoDB, or use Mongoose, the most popular MongoDB object modeling tool. In this chapter we’ll describe the latter, using the dedicated @nestjs/mongoose package.

Start by installing the required dependencies:

  1. $ npm install --save @nestjs/mongoose mongoose

Once the installation process is complete, we can import the MongooseModule into the root AppModule.

  1. @@filename(app.module)
  2. import { Module } from '@nestjs/common';
  3. import { MongooseModule } from '@nestjs/mongoose';
  4. @Module({
  5. imports: [MongooseModule.forRoot('mongodb://localhost/nest')],
  6. })
  7. export class AppModule {}

The forRoot() method accepts the same configuration object as mongoose.connect() from the Mongoose package, as described here.

Model injection

With Mongoose, everything is derived from a Schema. Let’s define the CatSchema:

  1. @@filename(schemas/cat.schema)
  2. import * as mongoose from 'mongoose';
  3. export const CatSchema = new mongoose.Schema({
  4. name: String,
  5. age: Number,
  6. breed: String,
  7. });

The cat.schema file resides in a folder in the cats directory, where we also define the CatsModule. While you can store schema files wherever you prefer, we recommend storing them them near their related domain objects, in the appropriate module directory.

Let’s look at the CatsModule:

  1. @@filename(cats.module)
  2. import { Module } from '@nestjs/common';
  3. import { MongooseModule } from '@nestjs/mongoose';
  4. import { CatsController } from './cats.controller';
  5. import { CatsService } from './cats.service';
  6. import { CatSchema } from './schemas/cat.schema';
  7. @Module({
  8. imports: [MongooseModule.forFeature([{ name: 'Cat', schema: CatSchema }])],
  9. controllers: [CatsController],
  10. providers: [CatsService],
  11. })
  12. export class CatsModule {}

The MongooseModule provides the forFeature() method to configure the module, including defining which models should be registered in the current scope. If you also want to use the models in another module, add MongooseModule to the exports section of CatsModule and import CatsModule in the other module.

Once you’ve registered the schema, you can inject a Cat model into the CatsService using the @InjectModel() decorator:

  1. @@filename(cats.service)
  2. import { Model } from 'mongoose';
  3. import { Injectable } from '@nestjs/common';
  4. import { InjectModel } from '@nestjs/mongoose';
  5. import { Cat } from './interfaces/cat.interface';
  6. import { CreateCatDto } from './dto/create-cat.dto';
  7. @Injectable()
  8. export class CatsService {
  9. constructor(@InjectModel('Cat') private readonly catModel: Model<Cat>) {}
  10. async create(createCatDto: CreateCatDto): Promise<Cat> {
  11. const createdCat = new this.catModel(createCatDto);
  12. return await createdCat.save();
  13. }
  14. async findAll(): Promise<Cat[]> {
  15. return await this.catModel.find().exec();
  16. }
  17. }
  18. @@switch
  19. import { Model } from 'mongoose';
  20. import { Injectable, Dependencies } from '@nestjs/common';
  21. import { getModelToken } from '@nestjs/mongoose';
  22. @Injectable()
  23. @Dependencies(getModelToken('Cat'))
  24. export class CatsService {
  25. constructor(catModel) {
  26. this.catModel = catModel;
  27. }
  28. async create(createCatDto) {
  29. const createdCat = new this.catModel(createCatDto);
  30. return await createdCat.save();
  31. }
  32. async findAll() {
  33. return await this.catModel.find().exec();
  34. }
  35. }

Testing

When unit testing an application, we usually want to avoid any database connection, making our test suites simpler to set up and faster to execute. But our classes might depend on models that are pulled from the connection instance. How do we resolve these classes? The solution is to create mock models.

To make this easier, the @nestjs/mongoose package exposes a getModelToken() function that returns a prepared injection token based on a token name. Using this token, you can easily provide a mock implementation using any of the standard custom provider techniques, including useClass, useValue, and useFactory. For example:

  1. @Module({
  2. providers: [
  3. CatsService,
  4. {
  5. provide: getModelToken('Cat'),
  6. useValue: catModel,
  7. },
  8. ],
  9. })
  10. export class CatsModule {}

In this example, a hardcoded catModel (object instance) will be provided whenever any consumer injects a Model<Cat> using an @InjectModel() decorator.

Async configuration

When you need to pass module options asynchronously instead of statically, use the forRootAsync() method. As with most dynamic modules, Nest provides several techniques to deal with async configuration.

One technique is to use a factory function:

  1. MongooseModule.forRootAsync({
  2. useFactory: () => ({
  3. uri: 'mongodb://localhost/nest',
  4. }),
  5. });

Like other factory providers, our factory function can be async and can inject dependencies through inject.

  1. MongooseModule.forRootAsync({
  2. imports: [ConfigModule],
  3. useFactory: async (configService: ConfigService) => ({
  4. uri: configService.getString('MONGODB_URI'),
  5. }),
  6. inject: [ConfigService],
  7. });

Alternatively, you can configure the MongooseModule using a class instead of a factory, as shown below:

  1. MongooseModule.forRootAsync({
  2. useClass: MongooseConfigService,
  3. });

The construction above instantiates MongooseConfigService inside MongooseModule, using it to create the required options object. Note that in this example, the MongooseConfigService has to implement the MongooseOptionsFactory interface, as shown below. The MongooseModule will call the createMongooseOptions() method on the instantiated object of the supplied class.

  1. @Injectable()
  2. class MongooseConfigService implements MongooseOptionsFactory {
  3. createMongooseOptions(): MongooseModuleOptions {
  4. return {
  5. uri: 'mongodb://localhost/nest',
  6. };
  7. }
  8. }

If you want to reuse an existing options provider instead of creating a private copy inside the MongooseModule, use the useExisting syntax.

  1. MongooseModule.forRootAsync({
  2. imports: [ConfigModule],
  3. useExisting: ConfigService,
  4. });

Example

A working example is available here.