Comment réutiliser correctement la connexion à Mongodb à travers l'application et les modules NodeJs

124

J'ai lu et lu et je ne sais toujours pas quelle est la meilleure façon de partager la même connexion de base de données (MongoDb) sur toute l'application NodeJs. Si je comprends bien, la connexion doit être ouverte lorsque l'application démarre et réutilisée entre les modules. Mon idée actuelle du meilleur moyen est que server.js(le fichier principal où tout commence) se connecte à la base de données et crée une variable objet qui est passée aux modules. Une fois connectée, cette variable sera utilisée par le code des modules si nécessaire et cette connexion reste ouverte. Par exemple:

    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

alors un autre module models/userressemble à ça:

Users = function(app, mongo) {

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

Users.prototype.getAll = function() {

    return "all users " + mongo.dbUsers;

    }
}

module.exports = Users;

Maintenant, j'ai horriblement l'impression que c'est faux, alors y a-t-il des problèmes évidents avec cette approche et si oui, comment l'améliorer?

Spirytus
la source
Le même genre de question que j'ai posée il y a quelques jours. stackoverflow.com/questions/24547357/…
Salvador Dali
Vérifiez le pilote mongoist . Il est " construit avec async / await à l'esprit " et permet d'exporter paresseusement une connexion comme module.exports = mongoist(connectionString);. (Lire à propos connectionStringdu manuel MongoDB.)
Alexandr Nil

Réponses:

150

Vous pouvez créer un mongoUtil.jsmodule qui a des fonctions pour à la fois se connecter à mongo et renvoyer une instance 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;
  }
};

Pour l'utiliser, vous le feriez dans votre app.js:

var mongoUtil = require( 'mongoUtil' );

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

Et puis, lorsque vous avez besoin d'accéder à mongo ailleurs, comme dans un autre .jsfichier, vous pouvez le faire:

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

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

La raison pour laquelle cela fonctionne est que dans node, quand les modules sont require'd, ils ne sont chargés / fournis qu'une seule fois, donc vous ne vous retrouverez jamais qu'avec une seule instance de _dbet mongoUtil.getDb()retournerez toujours cette même instance.

Remarque, code non testé.

go-oleg
la source
6
Excellent exemple! Cependant, j'ai une question. Comment cela fonctionnerait-il lors de l'exécution de votre application avec plusieurs clusters? Cela ferait-il tourner une autre instance de la connexion ou utiliserait-il simplement la connexion existante à partir de la source?
Farhan Ahmad
19
Comment géreriez-vous le cas où la connexion mongo s'éteindrait entre les deux? Tous les appels à getDb () échoueraient dans ce scénario jusqu'à ce que l'application de nœud soit redémarrée.
Ayan
4
J'ai essayé ce code mais je suis nul quand mongoUtil.getDb (), je ne sais pas pourquoi.
Keming
3
@KemingZeng - vous devez vous assurer que tous les modules qui utilisent mongoUtil sont importés app.jsdans la fonction de rappel de connectToServer. Si vous requireles définissez app.jsavant _db, vous obtiendrez des erreurs non définies dans les autres modules.
Mike R
2
À partir de la version 4 de mongoDB, il devrait l'être var database = mongoUtil.getDb(); database.db().collection( 'users' ).
Julian Veerkamp
26

Il existe de nombreuses façons de modifier cela pour accepter des objets de configuration à certains endroits, mais dans l'ensemble, c'est similaire à la façon dont vous disposez votre code, bien qu'avec une syntaxe JS plus moderne. Pourrait facilement être réécrit en prototypes et rappels, si tel est votre besoin.

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
la source
C'est l'approche la plus soignée que j'ai rencontrée
KalenGi
Je me rends compte que cette réponse date de presque un an et que je n'attends pas vraiment plus d'informations, mais cela semble être l'approche que j'aimerais le plus utiliser, mais je n'ai aucune chance de retirer l'objet Users déstructuré du fichier mongo. J'ai un fichier très similaire à votre someFile.js, mais la ligne 4 où vous appelez Users.addUser explose toujours pour moi - dit que Users est indéfini. Y a-t-il une pièce évidente qui me manque?
Rob E.
J'ai fini par créer une nouvelle question parce que cela me dérange tellement.
Rob E.
cela ne devrait pas fonctionner techniquement. Require met en cache l'objet lors du premier appel. Dans ce cas, il mettra uniquement en cache l'objet retourné par le constructeur. Appeler «init» plus tard n'a aucun effet sur ce qui sera retourné. Donc, ce const {Users} = require ('./ mongo') devrait échouer car il n'y aura pas de propriété 'User' sur le résultat mis en cache.
beNerd le
require.cache stocke une référence à l'objet, qui est partagée entre tous les fichiers qui nécessitent cet objet. Objets qui peuvent être mutés par des actions d'autres parties du programme (ou même eux-mêmes si vous utilisez des minuteries) Vous pouvez le tester vous-même rapidement, mais j'ai jeté un stylo rapide ensemble pour la démo: codesandbox.io/s/awesome-water-cexno
EddieDean
19

Voici comment je le fais avec la syntaxe contemporaine, basée sur l'exemple de go-oleg. Le mien est testé et fonctionnel.

J'ai mis quelques commentaires dans le code.

./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
la source
est-ce que la capture d'essai dans votre premier extrait de code est nécessaire? la fonction de connexion est une fonction asynchrone. L'erreur est déjà détectée à l'aide du rappel de style de nœud.
jarret
1
C'est une question très observatrice que j'aime. Je ne suis pas sûr que sans l'étudier de plus près dans l'habitat, vous placez le code. Il y aura un nombre limité de chemins que cela pourrait prendre pendant l'exécution du code. Je l'ai ajouté principalement pour montrer que vous pouviez y mettre un gestionnaire personnalisé et parce que j'incluais par défaut try / catch dans les fonctions asynchrones. C'est simplement un point d'accroche. Bonne question cependant. Je mettrai à jour si vous trouvez une note supplémentaire.
agm1984
chaque fois que j'appelle getDB (), cela créera de nouvelles connexions, non?
Vinay Pandya
18

Si vous utilisez Express, vous pouvez utiliser le module express-mongo-db qui vous permet d'obtenir une connexion db dans l'objet de requête.

Installer

npm install --save express-mongo-db

server.js

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

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

routes / users.js

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

go-oleg a fondamentalement raison, mais de nos jours, vous ne voulez (probablement) pas utiliser "mongodb" lui-même, mais plutôt utiliser un framework, qui fera beaucoup de "sale boulot" pour vous.

Par exemple, la mangouste est l'une des plus courantes. Voici ce que nous avons dans notre server.jsfichier initial :

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

C'est tout ce dont vous avez besoin pour le configurer. Maintenant, utilisez ceci n'importe où dans votre code

const mongoose = require('mongoose');

Et vous obtenez cette instance que vous avez configurée mongoose.connect

libik
la source
1
la mangouste est un ORM. Lisez ceci pour connaître les pièges possibles pour le même. Il ne fait aucun doute que les ORM sont excellents lorsqu'ils sont utilisés pour le processus de développement et d'apprentissage mais pas pour la production. Gardez cela à l'esprit
Saras Arya
1
La mangouste nécessite également des schémas. J'utilise le package MongoDB dans le cadre de la persistance polyglotte avec Neo4j, il est donc agréable de définir les propriétés du document selon les besoins.
agm1984
7

Initialisez la connexion comme une promesse:

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

Et puis appelez la connexion chaque fois que vous souhaitez effectuer une action sur la base de données:

    // 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
la source
7

Une solution testée basée sur la réponse acceptée:

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 - un itinéraire:

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
la source
Cette réponse est complète et fonctionnelle.
Ahmad Sharif
7

Voici ma configuration 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
la source
3

nous pouvons créer un fichier dbconnection comme 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;
        }

    };

puis utilisez ce fichier dans votre application comme

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

puis utilisez comme ceci dans votre fonction asynchrone

db  = await connection.connect();

j'espère que cela fonctionnera

gaurav
la source
2

Je suis un peu en retard pour cela, mais j'ajouterai aussi ma solution. C'est une approche beaucoup plus noble par rapport aux réponses ici.

Quoi qu'il en soit, si vous utilisez MongoDB version 4.0 et Node.js 3.0 (ou versions supérieures), vous pouvez utiliser la isConnected()fonction du 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");
}

CA marchait bien pour moi. J'espère que ça aide.

Roshana Pitigala
la source
2

Je suis en retard à la fête, mais j'espère que cette réponse aidera quelqu'un, c'est un code fonctionnel:

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}

Nous exportons une fonction pour se connecter au mongo et une autre pour obtenir l'instance de la connexion.

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

});

Nous devons faire l'exigence du module auth après avoir initialisé la connexion, sinon la fonction getDb retournera 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
la source
2

Comme cela est étiqueté avec Express, j'ai pensé mentionner qu'Express a une fonctionnalité intégrée pour partager des données entre les itinéraires. Il existe un objet appelé app.locals. Nous pouvons y attacher des propriétés et y accéder depuis l'intérieur de nos routes. Vous instanciez simplement votre connexion mongo dans votre fichier 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'));

Cette connexion à la base de données est désormais accessible dans vos itinéraires comme ci-dessous sans avoir besoin de créer et d'exiger des modules supplémentaires.

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

Cette méthode garantit que vous disposez d'une connexion à la base de données ouverte pendant toute la durée de votre application, sauf si vous choisissez de la fermer à tout moment. Il est facilement accessible req.app.locals.your-collectionet ne nécessite pas de modules supplémentaires.

Hoppo
la source
Je trouve que c'est l'approche la plus propre. Avons-nous des inconvénients possibles pour cette approche? Je l'utilise et me semble plutôt bien, je partagerais mes apprentissages.
Priya Ranjan Singh le
@PriyaRanjanSingh Pour être honnête, je ne connais aucun inconvénient, mais je ne suis en aucun cas un expert en la matière. J'ai découvert cette méthode après avoir fait des recherches car j'ai trouvé les autres méthodes malencontreuses et je cherchais un code plus propre et plus compréhensible pour mon propre bénéfice. J'espère que quelqu'un de plus compétent que moi pourra mettre en évidence s'il y a des inconvénients. J'utilise cette méthode sans aucun problème depuis un certain temps maintenant et cela semble bien fonctionner.
Hoppo le
1

Si vous optez pour l'utilisation de la mangouste dans votre application, modifiez votre fichier app.js avec l'extrait suivant

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

Étape suivante: définir les modèles pour votre application, les exiger et effectuer l'opération CRUD directement par exemple

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

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

Vous n'avez pas besoin de toujours vous connecter à mogoDB ...

Naresh_Varma
la source
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
la source
1
Pouvez-vous s'il vous plaît ajouter une brève description?
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
la source
0

Je trouve que cela fonctionne 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
la source
La réponse est étiquetée javascript, ne pensez pas qu'une réponse TypeScript est appropriée.
KPopOG