如何在NodeJS中处理Mongodb的全局连接是最佳方法?

25

我正在使用Node-Mongo-Native,并尝试设置一个全局连接变量,但我对两个可能的解决方案感到困惑。你们能帮我确定哪个是好的吗? 1. 解决方案(不好的地方在于每个请求都会尝试创建一个新连接。)

var express = require('express');  
var app = express();  
var MongoClient = require('mongodb').MongoClient;  
var assert = require('assert');

// Connection URL
var url = '[connectionString]]';

// start server on port 3000
app.listen(3000, '0.0.0.0', function() {  
  // print a message when the server starts listening
  console.log("server starting");
});

// Use connect method to connect to the server when the page is requested
app.get('/', function(request, response) {  
  MongoClient.connect(url, function(err, db) {
    assert.equal(null, err);
    db.listCollections({}).toArray(function(err, collections) {
        assert.equal(null, err);
        collections.forEach(function(collection) {
            console.log(collection);
        });
        db.close();
    })
    response.send('Connected - see console for a list of available collections');
  });
});
  1. 解决方案(在应用程序初始化时连接并将连接字符串分配给全局变量),但我认为将连接字符串分配给全局变量不是一个好主意。

    var mongodb; var url = '[connectionString]'; MongoClient.connect(url, function(err, db) {
    assert.equal(null, err); mongodb=db; } );

我想在应用程序初始化时创建连接,并在整个应用程序生命周期中使用。

请问您能帮忙吗?谢谢。


你可以创建一个仅包含数据库连接的文件,然后导入该连接变量,并在需要的任何地方使用该导入的变量。 - Udit Kumawat
@UditKumawat 是的,我已经这样做了,但是这个用于Node的mongo库有一个回调函数来连接,我需要使用它,所以我需要等待它连接,然后再启动应用程序。 - NitinD
你可以声明一个全局变量,然后在初始化连接变量之后使用它。 - Udit Kumawat
是的,我考虑过这个问题。但是,我偶然发现了这篇关于在Node.js中使用全局变量的文章Using Global Variables in Node.js - NitinD
7个回答

44

创建一个Connection单例模块来管理应用程序的数据库连接。

MongoClient不提供单例连接池,因此您不希望在应用程序中重复调用MongoClient.connect()。对于我看到的大多数应用程序而言,使用一个包装mongo客户端的单例类即可。

const MongoClient = require('mongodb').MongoClient

class Connection {

    static async open() {
        if (this.db) return this.db
        this.db = await MongoClient.connect(this.url, this.options)
        return this.db
    }

}

Connection.db = null
Connection.url = 'mongodb://127.0.0.1:27017/test_db'
Connection.options = {
    bufferMaxEntries:   0,
    reconnectTries:     5000,
    useNewUrlParser:    true,
    useUnifiedTopology: true,
}

module.exports = { Connection }

无论您在何处使用require('./Connection'),都将提供Connection.open()方法以及(如果已初始化)Connection.db属性。

const router = require('express').Router()
const { Connection } = require('../lib/Connection.js')

// This should go in the app/server setup, and waited for.
Connection.open()

router.get('/files', async (req, res) => {
   try {
     const files = await Connection.db.collection('files').find({})
     res.json({ files })
   }
   catch (error) {
     res.status(500).json({ error })
   }
})

module.exports = router

2
在应用程序初始化期间,只需要调用一次 connect,大约在 server.listenapp.listen 周围。然后该类作为单例,在每个需要 require 连接类的地方,都可以使用相同(已连接)的 database 属性。 - Matt
1
谢谢 @Matt。非常感激。 - NitinD
1
Connection.db.collection('files').find({}) ^类型错误:无法读取 null 的“collection”属性。 - Gustavo Piucco
有没有可能调整一下,使得数据库连接中不再“硬编码”数据库,例如对于本地连接,Connection.url 将是 mongodb://localhost:27017?这样数据库值就可以像 collection() 值一样通过传递给 Connection - user1063287
更新:FYI,我尝试了那个调整,它太长了无法作为评论,所以我将其发布为答案。如果您愿意,可以将内容添加到您的答案中,因为逻辑都来自您的答案。谢谢。 - user1063287
显示剩余7条评论

4

另一种更直接的方法是利用Express内置的功能,在应用程序的路由和模块之间共享数据。有一个名为app.locals的对象,我们可以将属性附加到它上面并从路由内部访问它。要使用它,请在您的app.js文件中实例化mongo连接。

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

现在您可以使用以下代码req.app.locals访问此数据库连接或其他数据,而无需创建和要求其他模块,这样就可以在路由中共享数据。

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

这种方法可以确保您在应用程序运行期间始终保持数据库连接,除非您选择在任何时候关闭它。 它可以通过req.app.locals.your-collection方便地访问,不需要创建任何额外的模块。


有没有办法在app.js之外实例化Mongo连接,例如在config/database.js中(只是为了保持文件更清洁)?我已经尝试过了,但我卡在如何在config/database.js中访问app上。 - user1063287

2

This is how i did.

// custom class
const MongoClient = require('mongodb').MongoClient
const credentials = "mongodb://user:pass@mongo"

class MDBConnect {
    static connect (db, collection) {
        return MongoClient.connect(credentials)
            .then( client => {
                return client.db(db).collection(collection);
            })
            .catch( err => { console.log(err)});
    }
    static findOne(db, collection, query) {
        return MDBConnect.connect(db,collection)
            .then(c => {
                return c.findOne(query)
                            .then(result => {
                                return result;
                            });
            })
    }
    // create as many as you want
    //static find(db, collection, query)
    //static insert(db, collection, query)
    // etc etc etc
}
module.exports = MDBConnect;


// in the route file
var express = require('express');
var router = express.Router();
var ObjectId = require('mongodb').ObjectId; 
var MDBConnect =  require('../storage/MDBConnect');

// Usages
router.get('/q/:id', function(req, res, next) {
    let sceneId = req.params.id;
    
    // user case 1
    MDBConnect.connect('gameapp','scene')
        .then(c => {
            c.findOne({_id: ObjectId(sceneId)})
                .then(result => {
                    console.log("result: ",result);
                    res.json(result);
                })
        });
    // user case 2, with query
    MDBConnect.findOne('gameapp','scene',{_id: ObjectId(sceneId)})
        .then(result => {
            res.json(result);
        });
});

1

模块版本 ^3.1.8

将连接初始化为 Promise:

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

然后,每当您希望在数据库上执行操作时,只需调用连接即可:

app.post('/insert', (req, res) => {
    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 - Vikalp Jain

0

我对这个问题进行了大量的研究,但是没有找到令我满意的解决方案,所以我自己开发了一个。

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

class DB {
    static database;
    static client;

    static async setUp(url) {
        if(!this.client) {
            await this.setClient(url);
            await this.setConnection();
        }

        return this.database;
    }

    static async setConnection() {
        this.database = this.client.db("default");
    }

    static async setClient(url) {
        console.log("Connecting to database");
        const client = new MongoClient(url);

        await client.connect();

        this.client = client;
    }
}

module.exports = DB;

使用方法:

const DB = require("./Path/to/DB");
(async () => {
  const database = await DB.setUp();
  const users = await database.collection("users").findOne({ email: "" });
});

0

这里是 Matt's answer 的一个版本,它允许您在使用连接时定义 databasecollection。不确定它是否像他的解决方案一样“完美无缺”,但它太长了,不能作为评论。

我删除了 Connection.options,因为它们给我带来了错误(也许某些选项已被弃用?)。

lib/Connection.js

const MongoClient = require('mongodb').MongoClient;
const { connection_string } = require('./environment_variables');

class Connection {
  static async open() {
    if (this.conn) return this.conn;
    this.conn = await MongoClient.connect(connection_string);
    return this.conn;
  }
}

Connection.conn = null;
Connection.url = connection_string;

module.exports = { Connection };

testRoute.js

const express = require('express');
const router = express.Router();
const { Connection } = require('../lib/Connection.js');

Connection.open();

router.route('/').get(async (req, res) => {
    try {
        const query = { username: 'my name' };
        const collection = Connection.conn.db('users').collection('users');
        const result = await collection.findOne(query);
        res.json({ result: result });
    } catch (error) {
        console.log(error);
        res.status(500).json({ error });
    }
});

module.exports = router;

如果您想将中间件从路由文件中分离出来:
testRoute.js变为:
const express = require('express');
const router = express.Router();
const test_middleware_01 = require('../middleware/test_middleware_01');

router.route('/').get(test_middleware_01);

module.exports = router;

中间件定义在 middleware/test_middleware_01.js 中:

const { Connection } = require('../lib/Connection.js');

Connection.open();

const test_middleware_01 = async (req, res) => {
    try {
        const query = { username: 'my name' };
        const collection = Connection.conn.db('users').collection('users');
        const result = await collection.findOne(query);
        res.json({ result: result });
    } catch (error) {
        console.log(error);
        res.status(500).json({ error });
    }
};

module.exports = test_middleware_01;

0
在 Express 中,您可以像这样添加 mongo 连接:
import {MongoClient} from 'mongodb';
import express from 'express';
import bodyParser from 'body-parser';
    let mongoClient = null;
    MongoClient.connect(config.mongoURL, {useNewUrlParser: true, useUnifiedTopology: true},function (err, client) {
        if(err) {
          console.log('Mongo connection error');
        } else {
          console.log('Connected to mongo DB');
          mongoClient = client;
        }
    })
let app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

app.use((req,res,next)=>{
    req.db = mongoClient.db('customer_support');
    next();
});

之后您可以通过 req.db 访问它。

router.post('/hello',async (req,res,next)=>{
    let uname = req.body.username;
    let userDetails = await getUserDetails(req.db,uname)
    res.statusCode = 200;
    res.data = userDetails;
    next();
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接