Come riutilizzare correttamente la connessione a Mongodb attraverso l'applicazione e i moduli di NodeJs


124

Ho letto e letto e sono ancora confuso su quale sia il modo migliore per condividere la stessa connessione di database (MongoDb) su tutta l'app NodeJs. Da quanto ho capito, la connessione dovrebbe essere aperta all'avvio dell'app e riutilizzata tra i moduli. La mia idea attuale del modo migliore è che server.js(il file principale dove tutto inizia) si connette al database e crea una variabile oggetto che viene passata ai moduli. Una volta connessa, questa variabile verrà utilizzata dal codice dei moduli se necessario e questa connessione rimane aperta. Per esempio:

    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

quindi un altro modulo ha questo models/useraspetto:

Users = function(app, mongo) {

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

Users.prototype.getAll = function() {

    return "all users " + mongo.dbUsers;

    }
}

module.exports = Users;

Ora ho la terribile sensazione che questo sia sbagliato, quindi ci sono problemi evidenti con questo approccio e, in caso affermativo, come migliorarlo?


Lo stesso tipo di domanda che ho fatto un paio di giorni fa. stackoverflow.com/questions/24547357/…
Salvador Dali

Controlla l' autista mangusta . È " costruito pensando a async / await " e consente di esportare pigramente connessioni come module.exports = mongoist(connectionString);. (Leggi connectionStringnel manuale MongoDB.)
Alexandr Nil

Risposte:


150

Puoi creare un mongoUtil.jsmodulo che abbia funzioni per connettersi a mongo e restituire un'istanza di 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;
  }
};

Per usarlo, dovresti farlo nel tuo app.js:

var mongoUtil = require( 'mongoUtil' );

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

E poi, quando hai bisogno di accedere a mongo da qualche altra parte, come in un altro .jsfile, puoi farlo:

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

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

Il motivo per cui funziona è che in node, quando i moduli sono require'd, vengono caricati / originati solo una volta, quindi finirai sempre con un'istanza di _dbe mongoUtil.getDb()restituirai sempre quella stessa istanza.

Nota, codice non testato.


6
Ottimo esempio! Tuttavia, ho una domanda. Come funzionerebbe quando si esegue la tua app con più cluster? Farebbe girare un'altra istanza della connessione o userebbe semplicemente la connessione esistente dalla sorgente?
Farhan Ahmad,

19
Come gestireste il caso quando la connessione mongo muore nel mezzo? Tutte le chiamate a getDb () fallirebbero in quello scenario fino a quando l'applicazione del nodo non viene riavviata.
Ayan

4
Ho provato questo codice ma ho ottenuto null quando eseguo mongoUtil.getDb (), non so perché.
Keming

3
@KemingZeng - devi assicurarti che tutti i moduli che usano mongoUtil siano importati nella app.jsfunzione di callback di connectToServer. Se requireli hai impostati app.jsprima _db, otterrai errori indefiniti negli altri moduli.
Mike R

2
A partire dalla versione 4 di mongoDB dovrebbe essere var database = mongoUtil.getDb(); database.db().collection( 'users' ).
Julian Veerkamp

26

Ci sono molti modi in cui questo potrebbe essere ottimizzato per accettare oggetti di configurazione in alcuni punti, ma nel complesso è simile a come hai disposto il tuo codice, anche se con una sintassi JS più moderna. Potrebbe essere facilmente riscritto in prototipi e callback, se questo è il tuo 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;
}

Questo è l'approccio più
accurato

Mi rendo conto che questa risposta è vecchia di quasi un anno e non mi aspetto davvero più informazioni, ma questo sembra l'approccio che mi piacerebbe di più usare, ma non ho fortuna a tirare fuori dal file mongo l'oggetto Users destrutturato. Ho un file molto simile al tuo someFile.js, ma la riga 4 in cui chiami Users.addUser esplode sempre per me - dice che Users non è definito. C'è un pezzo ovvio che mi manca?
Rob E.

Ho finito per creare una nuova domanda perché questo mi infastidisce così tanto.
Rob E.

questo non dovrebbe funzionare tecnicamente. Richiedi memorizza nella cache l'oggetto nella prima chiamata. In questo caso, memorizzerà nella cache solo l'oggetto restituito dal costruttore. Chiamare "init" in seguito non ha effetto su ciò che verrà restituito. Quindi questo const {Users} = require ('./ mongo') dovrebbe fallire poiché non ci sarà alcuna proprietà 'User' nel risultato memorizzato nella cache.
beNerd

require.cache memorizza un riferimento all'oggetto, che è condiviso tra tutti i file che richiedono quell'oggetto. Oggetti che possono essere modificati da azioni di altre parti del programma (o anche da soli se si utilizzano timer). Puoi testarlo da solo rapidamente, ma ho messo insieme una penna veloce per la demo: codesandbox.io/s/awesome-water-cexno
EddieDean

19

Ecco come lo faccio con la sintassi contemporanea, sulla base dell'esempio di go-oleg. Il mio è testato e funzionante.

Ho inserito alcuni commenti nel codice.

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

è necessario il tentativo di cattura nel tuo primo snippet? la funzione di connessione è una funzione asincrona. L'errore è già stato rilevato utilizzando il callback in stile nodo.
stinchi

1
È una domanda molto attenta che amo. Non sono sicuro che senza studiarlo più da vicino nell'habitat si collochi il codice. Ci sarà un numero limitato di percorsi che potrebbe richiedere durante l'esecuzione del codice. L'ho aggiunto principalmente per mostrare che potresti mettere un gestore personalizzato lì e perché di default includo try / catch nelle funzioni asincrone. È semplicemente un punto di aggancio. Bella domanda però. Aggiornerò se trovi una nota aggiuntiva.
agm1984

ogni volta che chiamo getDB () creerà nuove connessioni, giusto?
Vinay Pandya

18

Se stai usando Express, puoi usare il modulo express-mongo-db che ti permette di ottenere la connessione db nell'oggetto richiesta.

Installare

npm install --save express-mongo-db

server.js

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

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

Percorsi / users.js

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

8

go-oleg è fondamentalmente corretto, ma in questi giorni (probabilmente) non vuoi usare "mongodb" stesso, piuttosto usa qualche framework, che farà un sacco di "lavoro sporco" per te.

Ad esempio, la mangusta è una delle più comuni. Questo è ciò che abbiamo nel nostro server.jsfile iniziale :

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

Questo è tutto ciò che è necessario per configurarlo. Ora usalo ovunque nel tuo codice

const mongoose = require('mongoose');

E ottieni quell'istanza con cui hai impostato mongoose.connect


1
la mangusta è un ORM. Leggi questo per conoscere le possibili insidie ​​per lo stesso. Senza dubbio gli ORM sono ottimi se usati per il processo di sviluppo e apprendimento ma non per la produzione. Tienilo a mente
Saras Arya

1
Anche la mangusta richiede schemi. Sto usando il pacchetto MongoDB come parte della persistenza poliglotta con Neo4j, quindi è bello definire le proprietà del documento secondo necessità.
agm1984

7

Inizializza la connessione come una promessa:

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

E quindi chiama la connessione ogni volta che desideri eseguire un'azione sul database:

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

Una soluzione testata basata sulla risposta accettata:

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 - un percorso:

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;

Questa risposta è completa e funzionale.
Ahmad Sharif

7

Ecco la mia configurazione nel 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

possiamo creare un file dbconnection come 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;
        }

    };

e poi usa questo file nella tua app come

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

e poi usa in questo modo all'interno della tua funzione asincrona

db  = await connection.connect();

spero che funzioni


2

Sono un po 'in ritardo per questo, ma aggiungerò anche la mia soluzione. È un approccio molto più nobioso rispetto alle risposte qui.

Ad ogni modo, se stai usando MongoDB versione 4.0 e Node.js 3.0 (o versioni successive) puoi usare la isConnected()funzione dal 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");
}

Questo ha funzionato bene per me. Spero che sia d'aiuto.


2

Sono in ritardo alla festa, ma spero che questa risposta possa aiutare qualcuno, questo è un codice funzionale:

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}

Esportiamo una funzione per connetterci al mongo e un'altra per ottenere l'istanza della connessione.

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

});

Dobbiamo eseguire la richiesta del modulo auth dopo aver inizializzato la connessione, altrimenti la funzione getDb restituirà 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

Poiché questo è contrassegnato con Express, ho pensato di menzionare che Express ha una funzionalità incorporata per condividere i dati tra le rotte. C'è un oggetto chiamato app.locals. Possiamo allegare proprietà ad esso e accedervi dall'interno dei nostri percorsi. È sufficiente creare un'istanza della connessione mongo nel file 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'));

È ora possibile accedere a questa connessione al database all'interno delle rotte come di seguito senza la necessità di creare e richiedere moduli aggiuntivi.

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

Questo metodo garantisce che tu abbia una connessione al database aperta per la durata della tua app, a meno che tu non scelga di chiuderla in qualsiasi momento. È facilmente accessibile con req.app.locals.your-collectione non richiede moduli aggiuntivi.


Lo trovo l'approccio più pulito. Abbiamo qualche possibile svantaggio per questo approccio? Lo sto usando e mi sembra abbastanza buono, condividerei i miei insegnamenti.
Priya Ranjan Singh

@PriyaRanjanSingh Ad essere sincero, non conosco alcun inconveniente, ma non sono affatto un esperto in questo. Ho scoperto questo metodo dopo aver fatto delle ricerche, poiché ho trovato gli altri metodi poco piacevoli e cercavo un codice più pulito e comprensibile a mio vantaggio. Spero che qualcuno più esperto di me sarà in grado di evidenziare se ci sono degli inconvenienti.Ho usato questo metodo senza problemi anche se da un po 'di tempo e sembra funzionare bene.
Hoppo

1

Se scegli di utilizzare la mangusta nella tua applicazione, modifica il tuo file app.js con il seguente frammento

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

Passo successivo: definizione dei modelli per la tua applicazione li richiede ed esegui direttamente l'operazione CRUD, ad esempio

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

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

Non è necessario connettersi sempre a 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
Puoi aggiungere una breve descrizione?
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

Trovo che funzioni bene :)

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

La risposta è contrassegnata con javascript, non credo che una risposta TypeScript sia appropriata.
KPopOG
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.