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 i @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. Each schema maps to a MongoDB collection and defines the shape of the documents within that collection. Schemas are used to define Models. Models are responsible for creating and reading documents from the underlying MongoDB database.
Schemas can be created with NestJS decorators, or with Mongoose itself manually. Using decorators to create schemas greatly reduces boilerplate and improves overall code readability.
Let's define the CatSchema
:
@@filename(schemas/cat.schema)
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument } from 'mongoose';
export type CatDocument = HydratedDocument<Cat>;
@Schema()
export class Cat {
@Prop()
name: string;
@Prop()
age: number;
@Prop()
breed: string;
}
export const CatSchema = SchemaFactory.createForClass(Cat);
info Hint Note you can also generate a raw schema definition using the
DefinitionsFactory
class (from thenestjs/mongoose
). This allows you to manually modify the schema definition generated based on the metadata you provided. This is useful for certain edge-cases where it may be hard to represent everything with decorators.
The @Schema()
decorator marks a class as a schema definition. It maps our Cat
class to a MongoDB collection of the same name, but with an additional “s” at the end - so the final mongo collection name will be cats
. This decorator accepts a single optional argument which is a schema options object. Think of it as the object you would normally pass as a second argument of the mongoose.Schema
class' constructor (e.g., new mongoose.Schema(_, options)
)). To learn more about available schema options, see this chapter.
The @Prop()
decorator defines a property in the document. For example, in the schema definition above, we defined three properties: name
, age
, and breed
. The schema types for these properties are automatically inferred thanks to TypeScript metadata (and reflection) capabilities. However, in more complex scenarios in which types cannot be implicitly reflected (for example, arrays or nested object structures), types must be indicated explicitly, as follows:
@Prop([String])
tags: string[];
Alternatively, the @Prop()
decorator accepts an options object argument (read more about the available options). With this, you can indicate whether a property is required or not, specify a default value, or mark it as immutable. For example:
@Prop({ required: true })
name: string;
In case you want to specify relation to another model, later for populating, you can use @Prop()
decorator as well. For example, if Cat
has Owner
which is stored in a different collection called owners
, the property should have type and ref. For example:
import * as mongoose from 'mongoose';
import { Owner } from '../owners/schemas/owner.schema';
// inside the class definition
@Prop({ type: mongoose.Schema.Types.ObjectId, ref: 'Owner' })
owner: Owner;
In case there are multiple owners, your property configuration should look as follows:
@Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Owner' }] })
owners: Owner[];
Finally, the raw schema definition can also be passed to the decorator. This is useful when, for example, a property represents a nested object which is not defined as a class. For this, use the raw()
function from the @nestjs/mongoose
package, as follows:
@Prop(raw({
firstName: { type: String },
lastName: { type: String }
}))
details: Record<string, any>;
Alternatively, if you prefer not using decorators, you can define a schema manually. For example:
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 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 { Cat, CatSchema } from './schemas/cat.schema';
@Module({
imports: [MongooseModule.forFeature([{ name: Cat.name, 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 './schemas/cat.schema';
import { CreateCatDto } from './dto/create-cat.dto';
@Injectable()
export class CatsService {
constructor(@InjectModel(Cat.name) private catModel: Model<Cat>) {}
async create(createCatDto: CreateCatDto): Promise<Cat> {
const createdCat = new this.catModel(createCatDto);
return createdCat.save();
}
async findAll(): Promise<Cat[]> {
return this.catModel.find().exec();
}
}
@@switch
import { Model } from 'mongoose';
import { Injectable, Dependencies } from '@nestjs/common';
import { getModelToken } from '@nestjs/mongoose';
import { Cat } from './schemas/cat.schema';
@Injectable()
@Dependencies(getModelToken(Cat.name))
export class CatsService {
constructor(catModel) {
this.catModel = catModel;
}
async create(createCatDto) {
const createdCat = new this.catModel(createCatDto);
return createdCat.save();
}
async findAll() {
return this.catModel.find().exec();
}
}
Connection
At times you may need to access the native Mongoose Connection object. For example, you may want to make native API calls on the connection object. You can inject the Mongoose Connection by using the @InjectConnection()
decorator as follows:
import { Injectable } from '@nestjs/common';
import { InjectConnection } from '@nestjs/mongoose';
import { Connection } from 'mongoose';
@Injectable()
export class CatsService {
constructor(@InjectConnection() private connection: Connection) {}
}
Multiple databases
Some projects require multiple database connections. This can also be achieved with this module. To work with multiple connections, first create the connections. In this case, connection naming becomes mandatory.
@@filename(app.module)
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [
MongooseModule.forRoot('mongodb://localhost/test', {
connectionName: 'cats',
}),
MongooseModule.forRoot('mongodb://localhost/users', {
connectionName: 'users',
}),
],
})
export class AppModule {}