Reputation: 670
I am trying to create a database connection provider in Nestjs for MongoDB.
I inspected the user.controller.ts
and mongoDbProvider by putting breakpoints and found that the controller gets before the database connection is made. How do I make a database connection before the controllers get initialized?
Nest.js documentation says that useFactory method will run before any other module that depends on it.
src/mongo-db/mongodb.provider.ts
import { MongoClient } from "mongodb";
import { MONGODB_PROVIDER } from "../constants";
export const mongoDbProviders = [
{
provide: MONGODB_PROVIDER,
useFactory: async () => {
MongoClient.connect('mongodb://localhost:27017',
{ useUnifiedTopology: true },
(error, client) => {
return client.db('nestjs-sample');
});
}
},
];
src/mongo-db/mongo-db.module.ts
import { mongoDbProviders } from './mongo-db.providers';
@Module({
providers: [...mongoDbProviders],
exports: [...mongoDbProviders],
})
export class MongoDbModule {
}
src/constants.ts
export const MONGODB_PROVIDER = 'MONGODB_CONNECTION';
I imported MongoDbModule
into user.module.ts
src/user/user.module.ts
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserService } from './user.service';
import { MongoDbModule } from 'src/mongo-db/mongo-db.module';
@Module({
imports: [MongoDbModule],
controllers: [UserController],
providers: [UserService]
})
export class UserModule {}
Here I injected the db
from mongoDbProvider
into UserController
constructor. But the constructor runs before db connection.
src/user/user.controller.ts
import { Controller, Post, Req, Get, Res, Inject } from '@nestjs/common';
import { Request, Response } from "express";
import { MONGODB_PROVIDER } from 'src/constants';
@Controller('users')
export class UserController {
constructor(@Inject(MONGODB_PROVIDER) private readonly db: any) {
}
@Post()
async create(@Req() request: Request, @Res() response: Response) {
this.db.collection('users').insertOne(request.body, (err, result) => {
if (err) {
response.status(500).json(err);
} else {
response.status(201);
response.send(result);
}
});
}
@Get()
get(@Req() request: Request, @Res() response: Response) {
response.status(400).json({
message: 'kidilam service'
});
}
}
Upvotes: 6
Views: 16028
Reputation: 1626
I have written like this, which is working fine.
import { MongoClient } from "mongodb";
import { MONGODB_PROVIDER } from "../configs/constants";
import { Logger } from "@nestjs/common";
export const mongoDbProvider = [
{
provide: MONGODB_PROVIDER,
useFactory: async () => {
const client = new MongoClient('mongodb://localhost:27017');
try {
await client.connect();
Logger.log("Connected to Mongo Database");
return client.db('demo');
} catch (error) {
Logger.error(`Couldnt connect to Mysql ${error}`, "MongoProvider");
throw error;
}
},
},
];
Upvotes: 0
Reputation: 1
// We can also use MongoClient in nestJs using DipendencyInjection
// first we have to create database connection
// fileName :- db/dbConnection.service.ts
import { Injectable } from '@nestjs/common';
import { MongoClient } from 'mongodb';
import { config } from 'dotenv';
config();
let dbInstance;
const url = process.env.MONGO_URL || 'mongodb://localhost:27017/SocialUserEr';
@Injectable()
export class DbConnection { constructor() {
this.connect();
}
connect() {
const client = new MongoClient(url);
client
.connect()
.then((connection) => {
dbInstance = connection.db();
console.log('Database connection Succeeded');
})
.catch((err) => {
console.log(err);
});
}
db() {
if (dbInstance) return dbInstance;
}
}
Then we have to export it for use anywhere: ex
// fileName:- db/db.module.ts
import { Module } from '@nestjs/common';
import { DbConnection } from './db.service';
@Module({
imports: [],
exports: [DbConnection],
providers: [DbConnection],
})
export class DbModule {}
// for Using this connection inside a module you have to import DbModule : ex
// In this module we want to use db connection so we have to import db module
// fileName:- auth/auth.module.ts
import { Module } from '@nestjs/common';
import { DbModule } from 'src/db/db.module';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
@Module({
imports: [DbModule],
controllers: [AuthController],
providers: [AuthService],
})
export class AuthModule {}
// And lastly for using inside any module you have to just
// fileName :- auth.service.ts
import { Injectable } from '@nestjs/common';
import { DbConnection } from 'src/db/db.service';
@Injectable()
export class AuthService {
constructor(private database: DbConnection) {}
async createUser(userInfo) {
console.log(userInfo);
return await this.database.db().collection('user').insertOne(userInfo);
}
}
<!-- begin snippet: js hide: false console: true babel: false -->
Upvotes: 0
Reputation: 1
Just make you useFactory
await
export const databaseProviders = [
{
provide: "MAIN_DATABASE_CONNECTION",
useFactory: async (): Promise<mongodb.Db> => {
try {
const client = await mongodb.MongoClient.connect(process.env.CLUSTER, {
});
const db = client.db("dbName");
return db;
} catch (error) {
throw error;
}
},
}
]
Upvotes: -1
Reputation: 3105
Here is how I connect to MongoDB using NodeJS native driver with async/await
:
import { MongoClient, Db } from 'mongodb';
export const databaseProviders = [
{
provide: 'DATABASE_CONNECTION',
useFactory: async (): Promise<Db> => {
try {
const client = await MongoClient.connect('mongodb://localhost:27017', {
useUnifiedTopology: true,
});
return client.db('my_db');
} catch (e) {
throw e;
}
}
},
];
In this article I wrote there is a full implementation https://medium.com/@gusiol/nestjs-with-mongodb-native-driver-9d82e377d55.
Upvotes: 0
Reputation: 670
I found the solution. It is because the useFactory
expects a promise because it is an async
function. So I wrapped the MongoClient.connect()
inside a Promise
to resolve the database connection. Now it waits until the promise is resolved before initializing any modules that depend on it.
import { MongoClient } from "mongodb";
import { MONGODB_PROVIDER } from "../constants";
export const mongoDbProviders = [
{
provide: MONGODB_PROVIDER,
useFactory: async () => new Promise((resolve, reject) => {
MongoClient.connect('mongodb://localhost:27017',
{ useUnifiedTopology: true },
(error, client) => {
if (error) {
reject(error);
} else {
resolve(client.db('nestjs-sample'));
}
});
})
},
];
Upvotes: 4
Reputation: 7
It is because after the callback is called, the connection object returned is basically lost. If it is a must for you to use a factory provider, you could try using a closure in the callback to MongoClient.connect, together with a value provider. I mean with an approach such as this:
import { MongoClient } from "mongodb";
import { MONGODB_PROVIDER } from "../constants";
const MONGODB_PROVIDER_RESOLVED = 'MONGODB_PROVIDER_RESOLVED'
let connection = undefined;
export const mongoDbProviders = [
{
provide: MONGODB_PROVIDER_RESOLVED,
useValue: connection
},
{
provide: MONGODB_PROVIDER,
useFactory: async () => {
MongoClient.connect('mongodb://localhost:27017',
{ useUnifiedTopology: true },
(error, client) => {
connection = client.db('nestjs-sample');
});
}
},
];
Then inject both MONGODB_PROVIDER and MONGODB_PROVIDER_RESOLVED:
constructor(@Inject(MONGODB_PROVIDER) private readonly _db: any, @Inject(MONGODB_PROVIDER) private readonly db: any)
The first dependency injection will force your factory's code to run. The second will hold the resolved connection. That is a bit clumsy, I agree. What could be better would be probably using a class provider:
import { Injectable } from '@nestjs/common';
@Injectable()
export class MongoDBProvider {
connection
constructor(){
MongoClient.connect('mongodb://localhost:27017',
{ useUnifiedTopology: true },
((error, client) => {
this.connection = client.db('nestjs-sample');
}).bind(this));
}
}
You can now use it in your controller:
constructor( private readonly db: MongoDBProvider) {
}
Upvotes: 0