NestJS Integration
Integration
For integrating with NestJS, Mongoloquent provides the @mongoloqunet/nestjs package. NestJS is a progressive Node.js framework for building efficient, reliable and scalable server-side applications available for TypeScript. Since it's written in TypeScript, it integrates well with the Mongoloquent ORM.
To begin using it, we first install the required dependencies.
npm install @mongoloquent/nestjs mongoloquent mongodb
Once the installation process is complete, we can import the MongoloquentModule into the root AppModule.
- Typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { MongoloquentModule } from '@mongoloquent/nestjs';
@Module({
imports: [
MongoloquentModule.forRoot({
name: "default", // optional default: default,
connection: "mongodb://localhost:27017", // required
database: "mongoloquent", // required
timezone: "Asia/Jakarta", // optional default: Asia/Jakarta
models: [], // optional default: []
global: true // optional default: false
}),
],
})
export class AppModule {}
Once this is done, the Mongoloquent DB class will be available to inject across the entire project (without needing to import any modules), for example:
- Typescript
// app.service.ts
import { Injectable } from '@nestjs/common';
import { DB, InjectDB } from '@mongoloquent/nestjs';
@Injectable()
export class AppService {
constructor(
@InjectDB() private db: DB
) {}
}
The @InjectDB decorator can take a connectionName as an argument (default is "default") to specify the MongoDB connection configured in forRoot or forRootAsync.
Models
Mongoloquent implements the Active Record pattern. With this pattern, you use model classes directly to interact with the database. To continue the example, we need at least one model. Let's define the User model.
- Typescript
// users.model.ts
import { Model, IMongoloquentSchema, IMongoloquentTimestamps } from "mongoloquent"
export interface IUser extends IMongoloquentSchema, IMongoloquentTimestamps {
firstName: string;
lastName: string;
isActive: boolean;
}
export class User extends Model<IUser> {
public static $schema: IUser
protected $collection: string = "users"
}
Hint: Learn more about the Model here
The User model file sits in the users directory. This directory contains all files related to the UsersModule. You can decide where to keep your model files, however, we recommend creating them near their domain, in the corresponding module directory.
To begin using the User model, we need to let Mongoloquent know about it by inserting it into the models array in the module forRoot() method options:
- Typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { MongoloquentModule } from '@mongoloquent/nestjs';
import { User } from './users/user.model';
@Module({
imports: [
MongoloquentModule.forRoot({
name: "default",
connection: "mongodb://localhost:27017",
database: "mongoloquent",
timezone: "Asia/Jakarta",
models: [User],
global: true
}),
],
})
export class AppModule {}
Next, let's look at the UsersModule:
- Typescript
// users.module.ts
import { Module } from '@nestjs/common';
import { MongoloquentModule } from '@mongoloquent/nestjs';
import { User } from './user.model';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
imports: [MongoloquentModule.forFeature([User])],
providers: [UsersService],
controllers: [UsersController],
})
export class UsersModule {}
This module uses the forFeature() method to define which models are registered in the current scope. With that in place, we can inject the UserModel into the UsersService using the @InjectModel() decorator:
- Typescript
// users.services.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@mongoloquent/nestjs';
import { User } from './user.model';
import { ObjectId } from "mongodb"
@Injectable()
export class UsersService {
constructor(
@InjectModel(User) private userModel: typeof User,
) {}
async findAll() {
return this.userModel.get();
}
findOne(id: ObjectId) {
return this.userModel.where("_id", id).first();
}
async remove(id: ObjectId) {
const user = await this.userModel.find(id)
await user.delete()
}
}
Notice: Don't forget to import the UsersModule into the root AppModule.
If you want to use the model outside of the module which imports MongoloquentModule.forFeature, you'll need to re-export the providers generated by it. You can do this by exporting the whole module, like this:
- Typescript
// users.module.ts
import { Module } from '@nestjs/common';
import { MongoloquentModule } from '@mongoloquent/nestjs';
import { User } from './user.entity';
@Module({
imports: [MongoloquentModule.forFeature([User])],
exports: [MongoloquentModule]
})
export class UsersModule {}
Now if we import UsersModule in UserHttpModule, we can use @InjectModel(User) in the providers of the latter module.
- Typescript
// users.http.module.ts
import { Module } from '@nestjs/common';
import { UsersModule } from './users.module';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
@Module({
imports: [UsersModule],
providers: [UsersService],
controllers: [UsersController]
})
export class UserHttpModule {}
Relations
Relations are associations established between two or more tables. Relations are based on common fields from each table, often involving primary and foreign keys.
There are three types of relations:
| Relationship Type | Description |
|---|---|
| One-to-one | Every row in the primary table has one and only one associated row in the foreign table |
| One-to-many / Many-to-one | Every row in the primary table has one or more related rows in the foreign table |
| Many-to-many | Every row in the primary table has many related rows in the foreign table, and vice versa |
To define relations in models, use the corresponding methods. For example, to define that each User can have multiple photos, use the hasMany method.
- Typescript
// users.model.ts
import { Model, IMongoloquentSchema, IMongoloquentTimestamps } from "mongoloquent"
import { Photo } from '../photos/photos.model';
export interface IUser extends IMongoloquentSchema, IMongoloquentTimestamps {
firstName: string;
lastName: string;
isActive: boolean;
}
export default class User extends Model<IUser> {
public static $schema: IUser
protected $collection: string = "users"
public photos(){
return this.hasMany(Photo, "userId", "_id")
}
}
Hint: To learn more about relations in
Mongoloquent, read this chapter.
Transactions - Callback Approach
A database transaction symbolizes a unit of work performed within a database management system against a database, and treated in a coherent and reliable way independent of other transactions. A transaction generally represents any change in a database (learn more).
Below is a sample implementation of a managed transaction (auto-callback).
First, we need to inject the Mongoloquent DB class into a class in the normal way:
- Typescript
import { DB, InjectDB, InjectModel } from "@mongoloquent/nestjs"
import { User } from "./users.model"
@Injectable()
export class UsersService {
constructor(
@InjectDB() private db: DB,
@InjectModel(User) private userModel: typeof User,
) {}
}
Now, we can use this object to create a transaction.
- Typescript
async createMany() {
try {
await this.db.transaction(async session => {
await this.userModel.insert(
{ firstName: 'Abraham', lastName: 'Lincoln' },
{ session },
);
await this.userModel.insert(
{ firstName: 'John', lastName: 'Boothe' },
{ session },
);
});
} catch (err) {
// Transaction has been rolled back
// err is whatever rejected the promise chain returned to the transaction callback
}
}
Notice: The Mongoloquent DB class's transaction only working in the same connection.
Hint: To learn more about Mongoloquent Transaction read this chapter.
Transactions - Decorator Approach
In addition to the callback-based approach, the Mongoloquent DB class also supports transaction handling via decorators, allowing you to manage MongoDB transactions more declaratively in NestJS.
This approach removes the need to manually pass the session between layers — if you need the active session inside your service, you can simply retrieve it using getSession method from the DB class.
Adding a Transaction to a Controller Method
First, annotate your controller method with the @Transactional decorator to automatically run the method inside a managed transaction.
- Typescript
// users.controller.ts
import { Controller, Post } from '@nestjs/common'
import { ClientSession } from 'mongodb';
import { InjectModel, Transactional, Session } from '@mongoloquent/nestjs'
import { User } from './users.model'
import { PostsService } from './posts.service'
@Controller('users')
export class UsersController {
constructor(
@InjectModel(User) private userModel: typeof User,
private postsService: PostsService
) {}
@Post('create')
@Transactional()
async createUser(
@Session() Session: ClientSession
) {
await this.userModel.insert(
{ firstName: 'Abraham', lastName: 'Lincoln' },
{ session },
);
await this.postsService.createMany();
}
}
Accessing the Session in the Service Layer
In the service called from the controller, we can retrieve the session directly via getSession method from the DB class to perform additional operations within the same transaction.
- Typescript
// posts.service.ts
import { Injectable } from '@nestjs/common'
import { DB, InjectDB, InjectModel } from '@mongoloquent/nestjs'
import { Post } from './posts.model'
@Injectable()
export class PostsService {
constructor(
@InjectDB() private db: DB,
@InjectModel(Post) private postModel: typeof Post
) {}
async createMany() {
const session = this.db.getSession()
await this.postModel.insertMany(
[
{ title: "Traveling to Asia" },
{ title: "Traveling to Europe" },
],
{ session },
)
}
}
The @Transactional decorator accepts two optional parameters:
connectionName: The name of the MongoDB connection as configured inforRootorforRootAsync.retries: The number of times thetransactionshould be retried if it fails due to transient errors.
Notice: The
@Transactionaldecorator manages the transaction lifecycle automatically, committing if the method completes successfully and rolling back if an error is thrown.
Important: Just like the
callbackapproach, the MongoloquentDBclass'stransactionsonly work within the sameconnection.
Hint: To learn more about Mongoloquent Transaction read this chapter.
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.
Suppose you have an Album entity stored in its own database.
- Typescript
@Module({
imports: [
MongoloquentModule.forRoot({
name: 'default',
connection: 'user_db_host',
database: 'user_db',
timezone: 'Asia/Jakarta'
models: [User],
}),
MongoloquentModule.forRoot({
name: 'albumsConnection',
connection: 'album_db_host',
database: 'album_db',
timezone: 'Asia/Jakarta'
models: [Album],
}),
],
})
export class AppModule {}
Notice If you don't set the
namefor a connection, its name is set todefault. Please note that you shouldn't have multiple connections without a name, or with the same name, otherwise they will get overridden.
At this point, you have User and Album models registered with their own connection. With this setup, you have to tell the MongoloquentModule.forFeature() method. If you do not pass any connection name, the default connection is used.
- Typescript
@Module({
imports: [
MongoloquentModule.forFeature([User]),
MongoloquentModule.forFeature([Album], 'albumsConnection'),
],
})
export class AppModule {}
Async configuration
You may want to pass your MongoloquentModule options asynchronously instead of statically. In this case, use the forRootAsync() method, which provides several ways to deal with async configuration.
One approach is to use a factory function:
- Typescript
MongoloquentModule.forRootAsync({
useFactory: () => ({
connection: "mongodb://localhost:27017",
database: 'test',
timezone: "Asia/Jakarta"
}),
models: [],
});
Our factory behaves like any other asynchronous provider (e.g., it can be async and it's able to inject dependencies through inject).
- Typescript
MongoloquentModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
connection: "mongodb://localhost:27017",
database: 'test',
timezone: "Asia/Jakarta"
}),
models: [],
inject: [ConfigService],
});
Hint: Import
ConfigModuleandConfigServicefrom@nestjs/config
API Reference
You can find the API reference for the @mongoloquent/nestjs package here.
Support us
Mongoloquent is an MIT-licensed open source project. It can grow thanks to the support by these awesome people. If you'd like to join them, please read more here.
Sponsors
_
