Как правильно повторно использовать подключение к Mongodb в приложении и модулях NodeJs


124

Я читал и читал и до сих пор не понимаю, как лучше всего использовать одно и то же соединение с базой данных (MongoDb) во всем приложении NodeJs. Насколько я понимаю, соединение должно быть открыто при запуске приложения и повторно использоваться между модулями. Моя текущая идея наилучшего способа состоит в том, что server.js(главный файл, где все начинается) подключается к базе данных и создает объектную переменную, которая передается модулям. После подключения эта переменная будет использоваться кодом модулей по мере необходимости, и это соединение останется открытым. Например:

    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

то другой модуль models/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;

Теперь у меня ужасное чувство, что это неправильно, есть ли какие-то очевидные проблемы с этим подходом, и если да, то как сделать его лучше?


Тот же вопрос, который я задал пару дней назад. stackoverflow.com/questions/24547357/…
Сальвадор Дали

Проверьте водителя- монгиста . Он « построен с учетом async / await » и позволяет лениво экспортировать соединения вроде module.exports = mongoist(connectionString);. (Читайте об этом connectionStringв Руководстве по MongoDB.)
Александр Нил

Ответы:


151

Вы можете создать mongoUtil.jsмодуль, который имеет функции как для подключения к 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();

Причина, по которой это работает, заключается в том, что в узле, когда модули имеют значение required, они загружаются / загружаются только один раз, поэтому вы всегда будете иметь только один экземпляр _dbи mongoUtil.getDb()всегда будете возвращать тот же экземпляр.

Обратите внимание, код не тестировался.


6
Отличный пример! Однако у меня есть вопрос. Как это будет работать при запуске вашего приложения с несколькими кластерами? Будет ли он запускать другой экземпляр соединения или просто использовать существующее соединение из источника?
Фархан Ахмад

19
Как бы вы справились со случаем, когда соединение монго умирает между ними? В этом сценарии все вызовы getDb () завершатся ошибкой до перезапуска приложения узла.
Аян

4
Я пробовал этот код, но при выполнении mongoUtil.getDb () получил null, не знаю, почему.
Кеминг

3
@KemingZeng - вам необходимо убедиться, что все модули, использующие mongoUtil, импортированы в app.jsфункцию обратного вызова connectToServer. Если вы requireих app.jsранее _dbустановили, вы получите неопределенные ошибки в других модулях.
Mike R

2
Начиная с версии 4 mongoDB это должно быть var database = mongoUtil.getDb(); database.db().collection( 'users' ).
Джулиан Веркамп

26

Есть много способов, которыми это можно настроить, чтобы принимать объекты конфигурации в разных местах, но в целом это похоже на то, как у вас выложен ваш код, хотя и с более современным синтаксисом 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();

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

Это самый изящный подход, который я встречал
KalenGi

Я понимаю, что этому ответу почти год, и я действительно не ожидаю дополнительной информации, но похоже, что это подход, который я больше всего хотел бы использовать, но мне не повезло с извлечением деструктурированного объекта Users из файла mongo. У меня есть файл, очень похожий на ваш someFile.js, но строка 4, в которой вы вызываете Users.addUser, всегда взрывает меня - говорит, что Users не определено. Есть ли что-то очевидное, чего мне не хватает?
Роб Э.

В итоге я создал новый вопрос, потому что меня это очень беспокоит.
Роб Э.

это не должно работать технически. Требовать кеширует объект при первом вызове. В этом случае он будет кэшировать только объект, возвращенный конструктором. Вызов «init» позже не влияет на то, что будет возвращено. Таким образом, эта const {Users} = require ('./ mongo') должна завершиться ошибкой, поскольку в кешированном результате не будет никакого свойства 'User'.
beNerd

require.cache хранит ссылку на объект, которая используется всеми файлами, которым требуется этот объект. Объекты, которые могут быть изменены действиями из других частей программы (или даже сами по себе, если вы используете таймеры). Вы можете быстро проверить это сами, но я быстро собрал ручку для демонстрации: codeandbox.io/s/awesome-water-cexno
EddieDean

19

Вот как я это делаю с современным синтаксисом на примере 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 в вашем первом фрагменте? функция подключения - это асинхронная функция. Ошибка уже обнаруживается с помощью обратного вызова стиля узла.
Шанкс

1
Это очень наблюдательный вопрос, который мне нравится. Я не уверен, что не изучив его поближе, в среде обитания вы разместите код. Во время выполнения кода будет ограниченное количество путей. Я добавил его в основном для того, чтобы показать, что вы можете поместить туда собственный обработчик, и потому что по умолчанию я включаю try / catch в асинхронные функции. Это просто крючок. Но хороший вопрос. Я обновлю, если вы найдете дополнительную заметку.
agm1984

каждый раз, если я вызываю getDB (), он будет создавать новые соединения, верно?
Vinay

18

Если вы используете Express, вы можете использовать модуль express-mongo-db, который позволяет вам получить соединение с базой данных в объекте запроса.

устанавливать

npm install --save express-mongo-db

server.js

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

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

маршруты / users.js

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

8

go-oleg в основном прав, но в наши дни вы (вероятно) не хотите использовать сам «mongodb», а скорее используйте какой-то фреймворк, который сделает за вас много «грязной работы».

Например, мангуст - один из самых распространенных. Вот что у нас есть в нашем исходном server.jsфайле:

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

Это все, что нужно для его настройки. Теперь используйте это в любом месте вашего кода

const mongoose = require('mongoose');

И вы получите тот экземпляр, который настроили с помощью mongoose.connect


1
мангуст - это ORM. Прочтите это, чтобы узнать о возможных подводных камнях. Без сомнения, ORM хороши, когда используются для процесса разработки и обучения, но не для производства. Просто имейте это в виду
Сарас Арья

1
Для Mongoose также требуются схемы. Я использую пакет MongoDB как часть персистентности полиглота с Neo4j, поэтому неплохо определять свойства документа по мере необходимости.
agm1984

7

Инициализируйте соединение как обещание:

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

А затем вызывайте соединение всякий раз, когда хотите, чтобы вы выполнили действие с базой данных:

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

7

Протестированное решение, основанное на принятом ответе:

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;

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;

Это полный и функциональный ответ.
Ахмад Шариф

7

Вот моя установка в 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();

3

мы можем создать файл dbconnection, например 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;
        }

    };

а затем используйте этот файл в своем приложении, например

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

а затем используйте это внутри своей функции async

db  = await connection.connect();

надеюсь, это сработает


2

Я немного опоздал, но и свое решение добавлю. Это намного более простой подход по сравнению с ответами здесь.

В любом случае, если вы используете MongoDB версии 4.0 и Node.js 3.0 (или более поздние версии), вы можете использовать isConnected()функцию из 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");
}

У меня это сработало. Надеюсь, поможет.


2

Я опаздываю на вечеринку, но, надеюсь, этот ответ кому-то поможет, это функциональный код:

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}

Мы экспортируем одну функцию для подключения к mongo, а другую - для получения экземпляра подключения.

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

});

Мы должны выполнить требование модуля auth после инициализации соединения, иначе функция getDb вернет 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) {
        ...
    })
}

2

Поскольку это помечено Express, я подумал, что упомянул бы, что в Express есть встроенная функция для обмена данными между маршрутами. Есть объект под названием app.locals. Мы можем прикрепить к нему свойства и получить к нему доступ изнутри наших маршрутов. Вы просто создаете экземпляр своего mongo-соединения в файле 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'));

Теперь к этому соединению с базой данных можно получить доступ в пределах ваших маршрутов, как показано ниже, без необходимости создания дополнительных модулей.

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и не требует дополнительных модулей.


Я считаю это самым чистым подходом. Есть ли у этого подхода возможные недостатки? Я использую его, и мне кажется, что он выглядит неплохо, хотел бы поделиться своими знаниями.
Прия Ранджан Сингх,

1
@PriyaRanjanSingh Если честно, я не знаю никаких недостатков, но я ни в коем случае не эксперт в этом. Я обнаружил этот метод после исследования, так как я обнаружил другие методы, и я хотел очистить более понятный код для моей собственной выгоды. Надеюсь, кто-то более знающий, чем я, сможет выделить, есть ли какие-либо недостатки. Я использую этот метод без каких-либо проблем, хотя уже некоторое время, и, похоже, он работает хорошо.
Hoppo,

Это на самом деле замечательно, я использую его некоторое время после того, как узнал от вас. Одна вещь, которую я могу подумать в случае более крупного масштаба, заключается в том, что если у нас есть несколько экземпляров приложения, работающего за pm2 / навсегда, они не будут использоваться совместно. На данный момент это идеально :)
Прия Ранджан Сингх

1

Если вы решите использовать мангуст в своем приложении, отредактируйте файл app.js с помощью следующего фрагмента

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

Следующий шаг: определите модели для вашего приложения, которые требуют их, и выполните операцию CRUD напрямую, например

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

использование 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....
  }

Вам не нужно всегда подключаться к mogoDB ....


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

1
Не могли бы вы добавить краткое описание?
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)

0

Я считаю, что это хорошо работает :)

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

Ответ отмечен значком javascript, не думаю, что ответ TypeScript подходит.
KPopOG

0

Основываясь на принятых ответах, я использую простой подход. Но используйте это только в том случае, если вы хотите использовать dbвнутреннюю функцию, которая будет выполнена через некоторое время. Например: в функциях экспресс-маршрута это самый простой подход, который вы можете использовать.

mongo.js

const MongoClient = require("mongodb").MongoClient

var db

const connectDb = (callback) => {
    if (db) return callback()
    MongoClient.connect( uri, {ops}, 
        (err, database) => {
            if (err) return console.log(err)
            db = database.db("dbName") 
            console.log("Database Connected")
            callback()
        }
    )
}

const getDb = (collectionToGet) => {
    return db.collection(collectionToGet)
}

module.exports = {
    connectDb,
    getDb,
}

Теперь в других файлах, где вам нужен объект db,

user.js

const { connectDb, getDb } = require('mongo.js')

var db // store db object in this object
connectDb(() => ( db = getDb("user") ))

app.get('/', (req, res) => {
    // do something with req 
    db.insert({})
    // do something with res
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.