Como reutilizar adequadamente a conexão com o Mongodb em aplicativos e módulos NodeJs

124

Estive lendo e lendo e ainda estou confuso sobre qual é a melhor maneira de compartilhar a mesma conexão de banco de dados (MongoDb) em todo o aplicativo NodeJs. Pelo que entendi a conexão deve ser aberta quando o aplicativo é iniciado e reutilizada entre os módulos. Minha ideia atual da melhor maneira é que server.js(arquivo principal onde tudo começa) se conecte ao banco de dados e crie variáveis ​​de objeto que são passadas para os módulos. Uma vez conectada, esta variável será usada pelo código dos módulos conforme necessário e esta conexão permanecerá aberta. Por exemplo:

    var MongoClient = require('mongodb').MongoClient;
    var mongo = {}; // this is passed to modules and code

    MongoClient.connect("mongodb://localhost:27017/marankings", function(err, db) {
        if (!err) {
            console.log("We are connected");

            // these tables will be passed to modules as part of mongo object
            mongo.dbUsers = db.collection("users");
            mongo.dbDisciplines = db.collection("disciplines");

            console.log("aaa " + users.getAll()); // displays object and this can be used from inside modules

        } else
            console.log(err);
    });

    var users = new(require("./models/user"))(app, mongo);
    console.log("bbb " + users.getAll()); // not connected at the very first time so displays undefined

em seguida, outro módulo se models/userparece com isso:

Users = function(app, mongo) {

Users.prototype.addUser = function() {
    console.log("add user");
}

Users.prototype.getAll = function() {

    return "all users " + mongo.dbUsers;

    }
}

module.exports = Users;

Agora tenho uma sensação horrível de que isso é errado, então há algum problema óbvio com essa abordagem e, se houver, como torná-la melhor?

spirytus
fonte
O mesmo tipo de pergunta que fiz há alguns dias. stackoverflow.com/questions/24547357/…
Salvador Dali
Verifique o driver mongoist . É " construído com async / await em mente " e permite a conexão de exportação preguiçosa como module.exports = mongoist(connectionString);. (Leia sobre connectionStringno Manual do MongoDB.)
Alexandr Nil

Respostas:

150

Você pode criar um mongoUtil.jsmódulo que tenha funções para conectar ao mongo e retornar uma instância db do mongo:

const MongoClient = require( 'mongodb' ).MongoClient;
const url = "mongodb://localhost:27017";

var _db;

module.exports = {

  connectToServer: function( callback ) {
    MongoClient.connect( url,  { useNewUrlParser: true }, function( err, client ) {
      _db  = client.db('test_db');
      return callback( err );
    } );
  },

  getDb: function() {
    return _db;
  }
};

Para usá-lo, você faria isso em app.js:

var mongoUtil = require( 'mongoUtil' );

mongoUtil.connectToServer( function( err, client ) {
  if (err) console.log(err);
  // start the rest of your app here
} );

E então, quando você precisar acessar o mongo em outro lugar, como em outro .jsarquivo, você pode fazer isto:

var mongoUtil = require( 'mongoUtil' );
var db = mongoUtil.getDb();

db.collection( 'users' ).find();

A razão pela qual isso funciona é que no nó, quando os módulos são require'd', eles são carregados / originados apenas uma vez, então você sempre terminará com uma instância de _dbe mongoUtil.getDb()sempre retornará essa mesma instância.

Nota, código não testado.

go-oleg
fonte
6
Ótimo exemplo! No entanto, tenho uma pergunta. Como isso funcionaria ao executar seu aplicativo com vários clusters? Ele acionaria outra instância da conexão ou simplesmente usaria a conexão existente da fonte?
Farhan Ahmad
19
Como você lidaria com o caso quando a conexão mongo morresse no meio? Todas as chamadas para getDb () falhariam nesse cenário até que o aplicativo do nó fosse reiniciado.
Ayan
4
Tentei este código, mas obtive null quando fiz mongoUtil.getDb (), não sei por que isso.
Keming
3
@KemingZeng - você precisa se certificar de que todos os módulos que usam mongoUtil são importados app.jsdentro da função de retorno de chamada de connectToServer. Se você configurá- requirelos app.jsantes _db, obterá erros indefinidos nos outros módulos.
Mike R de
2
A partir do mongoDB versão 4, deveria ser var database = mongoUtil.getDb(); database.db().collection( 'users' ).
Julian Veerkamp
26

Há muitas maneiras de ajustar isso para aceitar objetos de configuração em locais, mas, no geral, é semelhante à forma como você organiza o código, embora com sintaxe JS mais moderna. Poderia ser facilmente reescrito em protótipos e callbacks, se essa for sua necessidade.

mongo.js

const { MongoClient } = require('mongodb');
const config = require('./config');
const Users = require('./Users');
const conf = config.get('mongodb');

class MongoBot {
  constructor() {
    const url = `mongodb://${conf.hosts.join(',')}`;

    this.client = new MongoClient(url, conf.opts);
  }
  async init() {
    await this.client.connect();
    console.log('connected');

    this.db = this.client.db(conf.db);
    this.Users = new Users(this.db);
  }
}

module.exports = new MongoBot();

Users.js

class User {
  constructor(db) {
    this.collection = db.collection('users');
  }
  async addUser(user) {
    const newUser = await this.collection.insertOne(user);
    return newUser;
  }
}
module.exports = User;

app.js

const mongo = require('./mongo');

async function start() {
  // other app startup stuff...
  await mongo.init();
  // other app startup stuff...
}
start();

someFile.js

const { Users } = require('./mongo');

async function someFunction(userInfo) {
  const user = await Users.addUser(userInfo);
  return user;
}
EddieDean
fonte
Esta é a abordagem mais
legal
Sei que essa resposta tem quase um ano e realmente não espero mais informações, mas essa parece ser a abordagem que mais gostaria de usar, mas não estou tendo sorte ao puxar o objeto Usuários desestruturados do arquivo mongo. Eu tenho um arquivo muito semelhante ao seu someFile.js, mas a linha 4, onde você chama Users.addUser, sempre explode para mim - diz que Usuários é indefinido. Existe uma peça óbvia que estou perdendo?
Rob E.
Acabei criando uma nova questão porque isso está me incomodando muito.
Rob E.
isso não deve funcionar tecnicamente. Requer caches do objeto na primeira chamada. Nesse caso, ele armazenará em cache apenas o objeto retornado pelo construtor. Chamar 'init' mais tarde não tem efeito sobre o que será retornado. Portanto, este const {Users} = require ('./ mongo') deve falhar, pois não haverá nenhuma propriedade 'User' no resultado armazenado em cache.
beNerd
require.cache armazena uma referência ao objeto, que é compartilhada entre todos os arquivos que requerem aquele objeto. Objetos que podem sofrer mutação por ações de outras partes do programa (ou até mesmo se você usar temporizadores). Você pode testar você mesmo rapidamente, mas juntei
EddieDean
19

Veja como faço isso com a sintaxe contemporânea, com base no exemplo de go-oleg. O meu é testado e funcional.

Eu coloquei alguns comentários no código.

./db/mongodb.js

 const MongoClient = require('mongodb').MongoClient
 const uri = 'mongodb://user:password@localhost:27017/dbName'
 let _db

 const connectDB = async (callback) => {
     try {
         MongoClient.connect(uri, (err, db) => {
             _db = db
             return callback(err)
         })
     } catch (e) {
         throw e
     }
 }

 const getDB = () => _db

 const disconnectDB = () => _db.close()

 module.exports = { connectDB, getDB, disconnectDB }

./index.js

 // Load MongoDB utils
 const MongoDB = require('./db/mongodb')
 // Load queries & mutations
 const Users = require('./users')

 // Improve debugging
 process.on('unhandledRejection', (reason, p) => {
     console.log('Unhandled Rejection at:', p, 'reason:', reason)
 })

 const seedUser = {
     name: 'Bob Alice',
     email: '[email protected]',
     bonusSetting: true
 }

 // Connect to MongoDB and put server instantiation code inside
 // because we start the connection first
 MongoDB.connectDB(async (err) => {
     if (err) throw err
     // Load db & collections
     const db = MongoDB.getDB()
     const users = db.collection('users')

     try {
         // Run some sample operations
         // and pass users collection into models
         const newUser = await Users.createUser(users, seedUser)
         const listUsers = await Users.getUsers(users)
         const findUser = await Users.findUserById(users, newUser._id)

         console.log('CREATE USER')
         console.log(newUser)
         console.log('GET ALL USERS')
         console.log(listUsers)
         console.log('FIND USER')
         console.log(findUser)
     } catch (e) {
         throw e
     }

     const desired = true
     if (desired) {
         // Use disconnectDB for clean driver disconnect
         MongoDB.disconnectDB()
         process.exit(0)
     }
     // Server code anywhere above here inside connectDB()
 })

./users/index.js

 const ObjectID = require('mongodb').ObjectID

 // Notice how the users collection is passed into the models
 const createUser = async (users, user) => {
     try {
         const results = await users.insertOne(user)
         return results.ops[0]
     } catch (e) {
         throw e
     }
 }

 const getUsers = async (users) => {
     try {
         const results = await users.find().toArray()
         return results
     } catch (e) {
         throw e
     }
 }

 const findUserById = async (users, id) => {
     try {
         if (!ObjectID.isValid(id)) throw 'Invalid MongoDB ID.'
         const results = await users.findOne(ObjectID(id))
         return results
     } catch (e) {
         throw e
     }
 }

 // Export garbage as methods on the Users object
 module.exports = { createUser, getUsers, findUserById }
agm1984
fonte
a captura de tentativa em seu primeiro trecho é necessária? a função de conexão é uma função assíncrona. O erro já está sendo detectado usando o retorno de chamada de estilo de nó.
Shanks
1
É uma pergunta muito observadora que adoro. Não tenho certeza sem estudá-lo mais de perto no habitat em que você coloca o código. Haverá um número limitado de caminhos que pode seguir durante a execução do código. Eu o adicionei principalmente para mostrar que você poderia colocar um manipulador personalizado lá e porque o padrão é incluir try / catch nas funções assíncronas. É simplesmente um ponto de gancho. Boa pergunta, entretanto. Vou atualizar se você encontrar uma nota adicional.
agm1984
toda vez que eu chamar getDB (), ele criará novas conexões, certo?
Vinay Pandya,
18

Se você estiver usando o Express, poderá usar o módulo express-mongo-db que permite obter a conexão db no objeto de solicitação.

Instalar

npm install --save express-mongo-db

server.js

var app = require('express')();

var expressMongoDb = require('express-mongo-db');
app.use(expressMongoDb('mongodb://localhost/test'));

rotas / users.js

app.get('/', function (req, res, next) {
    req.db // => Db object
});
Mukesh Chapagain
fonte
8

go-oleg está basicamente certo, mas atualmente você (provavelmente) não quer usar o "mongodb" propriamente dito, ao invés disso, usar algum framework, que fará muito "trabalho sujo" para você.

Por exemplo, o mangusto é um dos mais comuns. Isso é o que temos em nosso server.jsarquivo inicial :

const mongoose = require('mongoose');
const options = {server: {socketOptions: {keepAlive: 1}}};
mongoose.connect(config.db, options);

Isso é tudo o que é necessário para configurá-lo. Agora use isso em qualquer lugar do seu código

const mongoose = require('mongoose');

E você obtém aquela instância que configurou com mongoose.connect

libik
fonte
1
mangusto é um ORM. Leia isto para saber sobre possíveis armadilhas para o mesmo. Sem dúvida, os ORMs são ótimos quando usados ​​para processos de desenvolvimento e aprendizagem, mas não para produção. Basta ter isso em mente
Saras Arya
1
O Mongoose também requer esquemas. Estou usando o pacote MongoDB como parte da persistência poliglota com Neo4j, então é bom definir as propriedades do documento conforme necessário.
agm1984
7

Inicialize a conexão como uma promessa:

const MongoClient = require('mongodb').MongoClient
const uri = 'mongodb://...'
const client = new MongoClient(uri)
const connection = client.connect() // initialized connection

Em seguida, chame a conexão sempre que desejar, execute uma ação no banco de dados:

    // if I want to insert into the database...
    const connect = connection
    connect.then(() => {
        const doc = { id: 3 }
        const db = client.db('database_name')
        const coll = db.collection('collection_name')
        coll.insertOne(doc, (err, result) => {
            if(err) throw err
        })
    })
Henry Bothin
fonte
7

Uma solução testada com base na resposta aceita:

mongodbutil.js:

var MongoClient = require( 'mongodb' ).MongoClient;
var _db;
module.exports = {
  connectToServer: function( callback ) {
    MongoClient.connect( "<connection string>", function( err, client ) {
      _db = client.db("<collection name>");
      return callback( err );
    } );
  },
  getDb: function() {
    return _db;
  }
};

app.js:

var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var app = express();
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

var mongodbutil = require( './mongodbutil' );
mongodbutil.connectToServer( function( err ) {
  //app goes online once this callback occurs
  var indexRouter = require('./routes/index');
  var usersRouter = require('./routes/users');
  var companiesRouter = require('./routes/companies');
  var activitiesRouter = require('./routes/activities');
  var registerRouter = require('./routes/register');  
  app.use('/', indexRouter);
  app.use('/users', usersRouter);
  app.use('/companies', companiesRouter);
  app.use('/activities', activitiesRouter);
  app.use('/register', registerRouter);  
  // catch 404 and forward to error handler
  app.use(function(req, res, next) {
    next(createError(404));
  });
  // error handler
  app.use(function(err, req, res, next) {
    res.locals.message = err.message;
    res.locals.error = req.app.get('env') === 'development' ? err : {};
    res.status(err.status || 500);
    res.render('error');
  });
  //end of calback
});

module.exports = app;

Activities.js - uma rota:

var express = require('express');
var router = express.Router();
var mongodbutil = require( '../mongodbutil' );
var db = mongodbutil.getDb();

router.get('/', (req, res, next) => {  
    db.collection('activities').find().toArray((err, results) => {
        if (err) return console.log(err)
            res.render('activities', {activities: results, title: "Activities"})
    });
});

router.post('/', (req, res) => {
  db.collection('activities').save(req.body, (err, result) => {
    if (err) return console.log(err)
    res.redirect('/activities')
  })
});

module.exports = router;
Steve
fonte
Essa resposta é completa e funcional.
Ahmad Sharif
7

Aqui está minha configuração em 2020:

./utils/database.js

const { MongoClient } = require('mongodb');

class Mongo {
    constructor () {
        this.client = new MongoClient("mongodb://127.0.0.1:27017/my-app", {
            useNewUrlParser: true,
            useUnifiedTopology: true
        });
    }

    async main () {
        await this.client.connect();
        console.log('Connected to MongoDB');

        this.db = this.client.db();
    }
}

module.exports = new Mongo();

/app.js

const mongo = require('./utils/database');
const express = require('express');

const app = express();

const boot = async () => {
    await mongo.main();
    app.listen(3000);
};

boot();
Aditya Hajare
fonte
3

podemos criar um arquivo dbconnection como dbconnection.js

const MongoClient = require('mongodb').MongoClient
const mongo_url = process.env.MONGO_URL;

    module.exports = {
        connect: async function(callback) {
            var connection;
            await new Promise((resolve, reject) => {
                MongoClient.connect(mongo_url, {
                    useNewUrlParser: true
                }, (err, database) => {
                    if (err)
                        reject();
                    else {
                        connection = database;
                        resolve();
                    }
                });
            });
            return connection;
        }

    };

e usar este arquivo em seu aplicativo, como

var connection = require('../dbconnection');

e usar assim dentro de sua função assíncrona

db  = await connection.connect();

espero que funcione

Gaurav
fonte
2

Estou um pouco atrasado para isso, mas adicionarei minha solução também. É uma abordagem muito noobier em comparação com as respostas aqui.

De qualquer forma, se você estiver usando o MongoDB versão 4.0 e Node.js 3.0 (ou versões superiores), você pode usar a isConnected()função do MongoClient.

const MongoClient = require('mongodb').MongoClient;
const uri = "<your connection url>";
const client = new MongoClient(uri, { useNewUrlParser: true });

if (client.isConnected()) {
  execute();
} else {
  client.connect().then(function () {
    execute();
  });
}

function execute() {
    // Do anything here
    // Ex: client.db("mydb").collection("mycol");
}

Isso funcionou bem pra mim. Espero que ajude.

Roshana Pitigala
fonte
2

Estou atrasado para a festa, mas espero que esta resposta ajude alguém, este é um código funcional:

db.js

const MongoClient = require("mongodb").MongoClient
const urlMongo = "mongodb://localhost:27017"

var db;

function connectToServer( callback ) {
    MongoClient.connect(urlMongo,  { useUnifiedTopology: true , useNewUrlParser: true }, function( err, client ) {
        db  = client.db('auth');
        return callback( err );
    })
}

function getDb() {
    return db
}

module.exports = {connectToServer, getDb}

Exportamos uma função para conectar ao mongo e outra para obter uma instância da conexão.

app.js

const express = require('express')
const app = express()

const mongo = require('./db.js');

mongo.connectToServer( function( err) {
  if (err) console.log(err);
  const auth = require('./modulos')

  app.post('/login', (req, res) => { auth.login(req, res)})
  app.listen(3000, function () { console.log('Corriendo en puerto 3000')})

});

Devemos fazer o require do módulo auth após inicializarmos a conexão, caso contrário, a função getDb retornará indefinida.

module.js

const db = require('../db.js').getDb()
const usuariosCollection = db.collection('usuarios')

function login(req, res){
    usuariosCollection.find({ 'username': 'Fran' }).toArray(function (err, doc) {
        ...
    })
}
Knemay
fonte
2

Como isso está marcado com o Express, pensei em mencionar que o Express tem um recurso integrado para compartilhar dados entre as rotas. Existe um objeto chamado app.locals. Podemos anexar propriedades a ele e acessá-lo de dentro de nossas rotas. Você simplesmente instancia sua conexão mongo em seu arquivo app.js.

var app = express();

MongoClient.connect('mongodb://localhost:27017/')
.then(client =>{
  const db = client.db('your-db');
  const collection = db.collection('your-collection');
  app.locals.collection = collection;
});
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              // view engine setup
app.set('views', path.join(__dirname, 'views'));

Esta conexão de banco de dados agora pode ser acessada dentro de suas rotas como abaixo, sem a necessidade de criar e exigir módulos adicionais.

app.get('/', (req, res) => {
  const collection = req.app.locals.collection;
  collection.find({}).toArray()
  .then(response => res.status(200).json(response))
  .catch(error => console.error(error));
});

Este método garante que você tenha uma conexão de banco de dados aberta durante todo o seu aplicativo, a menos que você opte por fechá-la a qualquer momento. É facilmente acessível req.app.locals.your-collectione não requer módulos adicionais.

Hoppo
fonte
Acho que é a abordagem mais limpa. Temos alguma desvantagem possível para essa abordagem? Estou usando e parece muito bom para mim, gostaria de compartilhar meus aprendizados.
Priya Ranjan Singh
@PriyaRanjanSingh Para ser honesto, não conheço nenhuma desvantagem, mas não sou de forma alguma um especialista nisso. Descobri esse método depois de pesquisar, pois achei os outros métodos incompreensíveis e estava atrás de um código mais limpo e mais compreensível para meu próprio benefício. Espero que alguém com mais conhecimento do que eu seja capaz de destacar se há alguma desvantagem. Tenho usado esse método sem nenhum problema por um tempo e parece funcionar bem.
Hoppo
1

Se você optar por usar mongoose em seu aplicativo, edite seu arquivo app.js com o seguinte snippet

app.js

const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/Your_Data_Base_Name', {useNewUrlParser:true})
  .then((res) => {
    console.log(' ########### Connected to mongDB ###########');
  })
  .catch((err) => {
    console.log('Error in connecting to mongoDb' + err);
  });`

Próxima etapa: Definir modelos para sua aplicação, solicite-os e execute a operação CRUD diretamente, por exemplo

blogSchema.js

 const mongoose = require('mongoose');
 const Schema = mongoose.Schema;
 const blogSchema = new Schema({
     _id : mongoose.Schema.Types.ObjectId,
     title : {
        type : 'String',
        unique : true,
        required : true       
    },
    description : String,
        comments : [{type : mongoose.Schema.Types.ObjectId, ref: 'Comment'}]
 });
 module.exports = mongoose.model('Blog', blogSchema);

Uso createBlog.js

const Blog = require('../models/blogSchema');
exports.createBlog = (req, res, next) => {
const blog = new Blog({
  _id : new mongoose.Types.ObjectId,
  title : req.body.title,
  description : req.body.description,
});
blog.save((err, blog) => {
  if(err){
    console.log('Server Error save fun failed');
    res.status(500).json({
      msg : "Error occured on server side",
      err : err
    })
  }else{
    //do something....
  }

Você não precisa se conectar ao mogoDB sempre ....

Naresh_Varma
fonte
1
var MongoClient = require('mongodb').MongoClient;
var url = 'mongodb://localhost:27017/';
var Pro1;

module.exports = {
    DBConnection:async function()
    {
        Pro1 = new Promise(async function(resolve,reject){
            MongoClient.connect(url, { useNewUrlParser: true },function(err, db) {
                if (err) throw err;
                resolve(db);
            });        
        });
    },
    getDB:async function(Blockchain , Context)
    {
        bc = Blockchain;
        contx = Context;
        Pro1.then(function(_db)
        {
            var dbo = _db.db('dbname');
            dbo.collection('collectionname').find().limit(1).skip(0).toArray(function(err,result) {
                if (err) throw err;
                console.log(result);
            });
        });
    },
    closeDB:async function()
    {
        Pro1.then(function(_db){
            _db.close();
        });
    }
};
Tejas Naik
fonte
1
Você pode adicionar uma breve descrição?
RtmY de
1
const express = require('express')
const server = express()
const mongoClient = require('./MongoDB.js').client
const port = 3000
;(async () => {
    await mongoClient.connect()
    server.listen(port, () => console.log(`Server is listening on port ${port}!`))
})().catch(console.error)
Xulong Zhang
fonte
0

Acho que funciona bem :)

mongoUtil.ts

import { MongoClient } from 'mongodb';
const uri =
  'MONGOSTRING';

let connPoolPromise: any = null;

const mongoPoolPromise = () => {
  if (connPoolPromise) return connPoolPromise;

  connPoolPromise = new Promise((resolve, reject) => {
    const conn = new MongoClient(uri, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
    });

    if (conn.isConnected()) {
      return resolve(conn);
    } else {
      conn
        .connect()
        .then(() => {
          return resolve(conn.db('DATABASENAME'));
        })
        .catch(err => {
          console.log(err);
          reject(err);
        });
    }
  });

  return connPoolPromise;
};

export = {
  mongoPoolPromise,
};

anyFile.ts

const { mongoPoolPromise } = require('./mongoUtil');

async function getProducts() {
  const db = await mongoPoolPromise();
  const data = await db
    .collection('myCollection')
    .find({})
    .toArray();
  console.log(data);
  return data;
}

export { getProducts };
Adam91Holt
fonte
A resposta está marcada com javascript, não acho que uma resposta TypeScript seja apropriada.
KPopOG