如何跨 NodeJs 应用程序和module正确重用与 Mongodb 的连接

IT技术 javascript node.js mongodb express
2021-01-14 21:10:24

我一直在阅读和阅读,但仍然对在整个 NodeJs 应用程序中共享相同数据库 (MongoDb) 连接的最佳方式感到困惑。据我了解,当应用程序启动并在module之间重用时,连接应该是打开的。我目前对最佳方法的想法是server.js(一切开始的主文件)连接到数据库并创建传递给module的对象变量。连接后,module代码将根据需要使用此变量,并且此连接保持打开状态。例如:

    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

然后另一个modulemodels/user看起来像这样:

Users = function(app, mongo) {

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

Users.prototype.getAll = function() {

    return "all users " + mongo.dbUsers;

    }
}

module.exports = Users;

现在我有一种可怕的感觉,这是错误的,所以这种方法是否有任何明显的问题,如果有,如何使它变得更好?

6个回答

您可以创建一个mongoUtil.jsmodule,module具有连接到 mongo 并返回 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;
  }
};

要使用它,您可以在您的app.js

var mongoUtil = require( 'mongoUtil' );

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

然后,当你需要在其他地方访问 mongo 时,比如在另一个.js文件中,你可以这样做:

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

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

这样做的原因是,在节点中,当module被require'd 时,它们只会被加载/获取一次,所以你最终只会得到一个实例,_db并且mongoUtil.getDb()总是返回同一个实例。

请注意,代码未经测试。

从 mongoDB 版本 4 开始,它应该是var database = mongoUtil.getDb(); database.db().collection( 'users' ).
2021-03-31 21:10:24
我试过这段代码,但在执行 mongoUtil.getDb() 时我得到了空值,我不知道为什么会这样。
2021-04-01 21:10:24
@KemingZeng -你需要确保使用mongoUtil所有module都是进口app.js的回调函数中connectToServer如果您之前设置了require它们,那么您将在其他module中遇到未定义的错误。app.js_db
2021-04-03 21:10:24
很好的例子!不过,我有一个问题。在使用多个集群运行您的应用程序时,这将如何工作?它会启动另一个连接实例还是简单地使用来自源的现有连接?
2021-04-07 21:10:24
当 mongo 连接在两者之间消失时,您将如何处理这种情况?在这种情况下,所有对 getDb() 的调用都将失败,直到节点应用程序重新启动。
2021-04-09 21:10:24

可以通过多种方式对其进行调整以在某些地方接受配置对象,但总体而言,它与您的代码布局方式相似,尽管使用了更现代的 JS 语法。如果这是您的要求,可以轻松地将其重写为原型和回调。

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

用户.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;

应用程序.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;
}
我意识到这个答案已经快一年了,我真的不期待更多信息,但这似乎是我最喜欢使用的方法,但我将解构的 Users 对象从 mongo 文件中拉出的运气为零。我有一个与你的 someFile.js 非常相似的文件,但是你调用 Users.addUser 的第 4 行总是对我来说爆炸 - 说用户是未定义的。有没有我遗漏的明显部分?
2021-03-11 21:10:24
这在技术上不应该起作用。Require 在第一次调用时缓存对象。在这种情况下,它只会缓存构造函数返回的对象。稍后调用 'init' 对返回的内容没有影响。所以这个 const { Users } = require('./mongo') 应该失败,因为缓存的结果上不会有任何 'User' 属性。
2021-03-12 21:10:24
是否可以使用此方法检测连接何时断开?
2021-03-27 21:10:24
这是我遇到的最巧妙的方法
2021-03-29 21:10:24
我最终创建了一个新问题,因为这让我非常烦恼。
2021-04-02 21:10:24

如果您使用 Express,那么您可以使用mongo-express-reqmodule,该module允许您在请求对象中获取数据库连接。

安装

npm install --save mongo-express-req

服务器.js

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

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

路线/用户.js

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

注意:mongo-express-req未维护的叉子express-mongo-db

根据 go-oleg 的示例,这是我如何使用现代语法来实现的。我的已经过测试并且可以正常使用。

我在代码中添加了一些注释。

./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: 'test@dev.null',
     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 }
你的第一个片段中的 try catch 是必要的吗?connect 函数是一个异步函数。该错误已被使用节点样式回调捕获。
2021-03-16 21:10:24
这是我喜欢的一个非常敏锐的问题。如果没有在您放置代码的栖息地中更仔细地研究它,我不确定。在代码执行期间,它可以采用的路径数量有限。我添加它主要是为了表明你可以在那里放置一个自定义处理程序,因为我默认在异步函数中包含 try/catch。它只是一个挂钩点。好问题。如果您找到附加说明,我会更新。
2021-03-18 21:10:24
每次我调用 getDB() 都会创建新的连接,对吗?
2021-04-04 21:10:24

基于已接受答案的经过测试的解决方案:

mongodbutil.js:

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

应用程序.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;

activity.js——一条路线:

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;
这个答案是完整且实用的。
2021-03-15 21:10:24