Cómo reutilizar correctamente la conexión a Mongodb en la aplicación y los módulos de NodeJs

124

He estado leyendo y leyendo y todavía estoy confundido sobre cuál es la mejor manera de compartir la misma conexión de base de datos (MongoDb) en toda la aplicación NodeJs. Según tengo entendido, la conexión debe estar abierta cuando se inicia la aplicación y reutilizarse entre módulos. Mi idea actual de la mejor manera es que server.js(el archivo principal donde comienza todo) se conecta a la base de datos y crea una variable de objeto que se pasa a los módulos. Una vez conectada, esta variable será utilizada por el código de los módulos según sea necesario y esta conexión permanece abierta. P.ej:

    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

entonces otro módulo se models/userve así:

Users = function(app, mongo) {

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

Users.prototype.getAll = function() {

    return "all users " + mongo.dbUsers;

    }
}

module.exports = Users;

Ahora tengo la horrible sensación de que esto está mal, ¿hay algún problema obvio con este enfoque y, de ser así, cómo mejorarlo?

spirytus
fuente
El mismo tipo de pregunta que hice hace un par de días. stackoverflow.com/questions/24547357/…
Salvador Dali
Compruebe el conductor mongoist . Está " construido con async / await en mente " y permite exportar perezosamente conexiones como module.exports = mongoist(connectionString);. (Lea sobre esto connectionStringen el Manual de MongoDB.)
Alexandr Nil

Respuestas:

150

Puede crear un mongoUtil.jsmódulo que tenga funciones para conectarse a mongo y devolver una instancia de mongo db:

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 usarlo, debe hacer esto en su app.js:

var mongoUtil = require( 'mongoUtil' );

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

Y luego, cuando necesite acceder a mongo en otro lugar, como en otro .jsarchivo, puede hacer esto:

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

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

La razón por la que esto funciona es que en el nodo, cuando los módulos son require'd, solo se cargan / obtienen una vez, por lo que solo terminará con una instancia de _dby mongoUtil.getDb()siempre devolverá la misma instancia.

Tenga en cuenta, código no probado.

go-oleg
fuente
6
¡Gran ejemplo! Sin embargo, tengo una pregunta. ¿Cómo funcionaría esto al ejecutar su aplicación con múltiples clústeres? ¿Activaría otra instancia de la conexión o simplemente usaría la conexión existente desde la fuente?
Farhan Ahmad
19
¿Cómo manejaría el caso cuando la conexión mongo muere en el medio? Todas las llamadas a getDb () fallarían en ese escenario hasta que se reinicie la aplicación del nodo.
Ayan
4
Probé este código pero obtuve un valor nulo cuando mongoUtil.getDb (), no sé por qué es eso.
Keming
3
@KemingZeng: debe asegurarse de que todos los módulos que usan mongoUtil se importen app.jsdentro de la función de devolución de llamada de connectToServer. Si requirelos configura app.jsantes _db, obtendrá errores indefinidos en los otros módulos.
Mike R
2
A partir de la versión 4 de mongoDB debería serlo var database = mongoUtil.getDb(); database.db().collection( 'users' ).
Julian Veerkamp
26

Hay muchas formas en que esto podría modificarse para aceptar objetos de configuración en algunos lugares, pero en general es similar a cómo tiene su código diseñado, aunque con una sintaxis JS más moderna. Podría reescribirse fácilmente en prototipos y devoluciones de llamada, si ese es su requisito.

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
fuente
Este es el enfoque más elegante que he encontrado
KalenGi
Me doy cuenta de que esta respuesta tiene casi un año y realmente no espero más información, pero este parece ser el enfoque que más me gustaría usar, pero no tengo suerte al sacar el objeto de usuarios desestructurado del archivo mongo. Tengo un archivo muy similar a su someFile.js, pero la línea 4 donde llama a Users.addUser siempre me explota, dice que Users no está definido. ¿Hay una pieza obvia que me falta?
Rob E.
Terminé creando una nueva pregunta porque esto me está molestando mucho.
Rob E.
esto no debería funcionar técnicamente. Requerir almacena en caché el objeto en la primera llamada. En este caso, solo almacenará en caché el objeto devuelto por el constructor. Llamar a 'init' más tarde no tiene ningún efecto sobre lo que se devolverá. Así que esta const {Users} = require ('./ mongo') debería fallar ya que no habrá ninguna propiedad de 'Usuario' en el resultado en caché.
beNerd
require.cache almacena una referencia al objeto, que se comparte entre todos los archivos que requieren ese objeto. Objetos que pueden ser modificados por acciones de otras partes del programa (o incluso ellos mismos si usa temporizadores). Puede probarlo usted mismo rápidamente, pero junté
EddieDean
19

Así es como lo hago con sintaxis contemporánea, basado en el ejemplo de go-oleg. El mío está probado y es funcional.

Pongo algunos comentarios en el 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
fuente
¿Es necesario el intento de captura en su primer fragmento? la función de conexión es una función asíncrona. El error ya se está detectando utilizando la devolución de llamada de estilo de nodo.
shanks
1
Es una pregunta muy observadora que me encanta. No estoy seguro de que sin estudiarlo más de cerca en el hábitat coloque el código. Habrá un número limitado de rutas que podría tomar durante la ejecución del código. Lo agregué principalmente para mostrar que podría poner un controlador personalizado allí y porque por defecto incluyo try / catch en funciones asincrónicas. Es simplemente un punto de gancho. Aunque es una buena pregunta. Actualizaré si encuentra una nota adicional.
agm1984
cada vez que llamo a getDB () se crearán nuevas conexiones, ¿verdad?
Vinay Pandya
18

Si está utilizando Express, puede utilizar el módulo express-mongo-db que le permite obtener una conexión db en el objeto de solicitud.

Instalar en pc

npm install --save express-mongo-db

server.js

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

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

rutas / usuarios.js

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

go-oleg es básicamente correcto, pero en estos días (probablemente) no quieras usar "mongodb" en sí, sino usar algún marco, que hará mucho "trabajo sucio" por ti.

Por ejemplo, la mangosta es una de las más comunes. Esto es lo que tenemos en nuestro server.jsarchivo inicial :

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

Esto es todo lo que se necesita para configurarlo. Ahora usa esto en cualquier lugar de tu código

const mongoose = require('mongoose');

Y obtienes esa instancia que configuraste con mongoose.connect

libik
fuente
1
mangosta es un ORM. Lea esto para conocer las posibles trampas de la misma. Sin duda, los ORM son excelentes cuando se utilizan para el proceso de desarrollo y aprendizaje, pero no para la producción. Solo tenga esto en cuenta
Saras Arya
1
Mangosta también requiere esquemas. Estoy usando el paquete MongoDB como parte de la persistencia políglota con Neo4j, por lo que es bueno definir las propiedades del documento según sea necesario.
agm1984
7

Inicialice la conexión como una promesa:

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

Y luego llame a la conexión cada vez que desee realizar una acción en la base de datos:

    // 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
fuente
7

Una solución probada basada en la respuesta aceptada:

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 - una ruta:

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
fuente
Esta respuesta es completa y funcional.
Ahmad Sharif
7

Aquí está mi configuración en 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
fuente
3

podemos crear un archivo 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;
        }

    };

y luego use este archivo en su aplicación como

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

y luego use así dentro de su función asincrónica

db  = await connection.connect();

espero que esto funcione

Gaurav
fuente
2

Llego un poco tarde para esto, pero también agregaré mi solución. Es un enfoque mucho más novedoso en comparación con las respuestas aquí.

De todos modos, si está utilizando MongoDB versión 4.0 y Node.js 3.0 (o versiones superiores), puede usar la isConnected()función de 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");
}

Esto funcionó bien para mi. Espero eso ayude.

Roshana Pitigala
fuente
2

Llego tarde a la fiesta, pero espero que esta respuesta ayude a alguien, este es un 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 una función para conectarnos al mongo y otra para obtener la instancia de la conexión.

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')})

});

Debemos hacer el requerimiento del módulo de autenticación después de inicializar la conexión, de lo contrario, la función getDb devolverá undefined.

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
fuente
2

Como está etiquetado con Express, pensé en mencionar que Express tiene una función incorporada para compartir datos entre rutas. Hay un objeto llamado app.locals. Podemos adjuntarle propiedades y acceder a él desde dentro de nuestras rutas. Simplemente crea una instancia de su conexión mongo en su archivo 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'));

Ahora se puede acceder a esta conexión de base de datos dentro de sus rutas como se muestra a continuación sin la necesidad de crear y requerir módulos adicionales.

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 garantiza que tenga una conexión a la base de datos abierta mientras dure su aplicación, a menos que elija cerrarla en cualquier momento. Es de fácil acceso req.app.locals.your-collectiony no requiere módulos adicionales.

Hoppo
fuente
Lo encuentro el enfoque más limpio. ¿Tenemos posibles inconvenientes para este enfoque? Lo estoy usando y me parece bastante bueno, compartiría mis aprendizajes.
Priya Ranjan Singh
@PriyaRanjanSingh Para ser honesto, no conozco ningún inconveniente, pero de ninguna manera soy un experto en esto. Descubrí este método después de investigar, ya que encontré los otros métodos de manera poco agradable y buscaba un código más limpio y comprensible para mi propio beneficio. Con suerte, alguien con más conocimientos que yo podrá resaltar si hay algún inconveniente. He estado usando este método sin ningún problema por un tiempo y parece que funciona bien.
Hoppo
1

Si opta por usar mongoose en su aplicación, edite su archivo app.js con el siguiente fragmento

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);
  });`

Siguiente paso: defina modelos para su aplicación, solicítelos y realice la operación CRUD directamente, por ejemplo

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....
  }

No es necesario que se conecte siempre a mogoDB ...

Naresh_Varma
fuente
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
fuente
1
¿Puede agregar una breve descripción?
RtmY
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
fuente
0

Encuentro que esto funciona bien :)

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
fuente
La respuesta está etiquetada con javascript, no creo que una respuesta de TypeScript sea apropiada.
KPopOG