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:
$ npm install --save @nestjs/mongoose mongoose
Once the installation process is complete, we can import the MongooseModule
into the root AppModule
.
@@filename(app.module)
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [MongooseModule.forRoot('mongodb://localhost/nest')],
})
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
:
@@filename(schemas/cat.schema)
import * as mongoose from 'mongoose';
export const CatSchema = new mongoose.Schema({
name: String,
age: Number,
breed: String,
});
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
:
@@filename(cats.module)
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
import { CatSchema } from './schemas/cat.schema';
@Module({
imports: [MongooseModule.forFeature([{ name: 'Cat', schema: CatSchema }])],
controllers: [CatsController],
providers: [CatsService],
})
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:
@@filename(cats.service)
import { Model } from 'mongoose';
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Cat } from './interfaces/cat.interface';
import { CreateCatDto } from './dto/create-cat.dto';
@Injectable()
export class CatsService {
constructor(@InjectModel('Cat') private readonly catModel: Model<Cat>) {}
async create(createCatDto: CreateCatDto): Promise<Cat> {
const createdCat = new this.catModel(createCatDto);
return await createdCat.save();
}
async findAll(): Promise<Cat[]> {
return await this.catModel.find().exec();
}
}
@@switch
import { Model } from 'mongoose';
import { Injectable, Dependencies } from '@nestjs/common';
import { getModelToken } from '@nestjs/mongoose';
@Injectable()
@Dependencies(getModelToken('Cat'))
export class CatsService {
constructor(catModel) {
this.catModel = catModel;
}
async create(createCatDto) {
const createdCat = new this.catModel(createCatDto);
return await createdCat.save();
}
async findAll() {
return await this.catModel.find().exec();
}
}
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:
@Module({
providers: [
CatsService,
{
provide: getModelToken('Cat'),
useValue: catModel,
},
],
})
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:
MongooseModule.forRootAsync({
useFactory: () => ({
uri: 'mongodb://localhost/nest',
}),
});
Like other factory providers, our factory function can be async
and can inject dependencies through inject
.
MongooseModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
uri: configService.getString('MONGODB_URI'),
}),
inject: [ConfigService],
});
Alternatively, you can configure the MongooseModule
using a class instead of a factory, as shown below:
MongooseModule.forRootAsync({
useClass: MongooseConfigService,
});
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.
@Injectable()
class MongooseConfigService implements MongooseOptionsFactory {
createMongooseOptions(): MongooseModuleOptions {
return {
uri: 'mongodb://localhost/nest',
};
}
}
If you want to reuse an existing options provider instead of creating a private copy inside the MongooseModule
, use the useExisting
syntax.
MongooseModule.forRootAsync({
imports: [ConfigModule],
useExisting: ConfigService,
});
Example
A working example is available here.