diffuse
diffuse

Reputation: 670

How to create a mongodb connection provider in Nestjs

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

Answers (6)

zulqarnain
zulqarnain

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

Abuzar Shabab
Abuzar Shabab

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

Muhammad Khalil
Muhammad Khalil

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

guizo
guizo

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

diffuse
diffuse

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

Patrick Assoa Adou
Patrick Assoa Adou

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

Related Questions