I've read a few guides on how to use Mongo with Node, and they all seem to connect to databases differently. One particular way that worked well for me was:

MongoClient.connect("mongodb://localhost:27017/exampleDb", function(err, db) {
  if(err) { return console.dir(err); }

  db.createCollection('users', function(err, collection) {});

  //Do all server/database operations in here

});

However, this seems inefficient/odd to me, I would have to reconnect to the database every time there is an app.get(), like for making a new user or retrieving information.

Another way that seems better suited to me is

var mongoose = require("mongoose")
var db = mongoose.connect("localhost:27107/users");

db.createCollection('users', function(err, collection) {});

I've seen several sites do something along these lines, but I personally can't get the above to work. I keep getting the error TypeError: db.createCollection is not a function server-side. So, my question is why the above code doesn't work, if the first code is a good alternative, and if there are any other ways to do this.

Solution 1

You can use a global variable to hold the connection (e.g. db), for example:

var db = null // global variable to hold the connection

MongoClient.connect('mongodb://localhost:27017/', function(err, client) {
    if(err) { console.error(err) }
    db = client.db('test') // once connected, assign the connection to the global variable
})

app.get('/', function(req, res) {
    db.collection('test').find({}).toArray(function(err, docs) {
        if(err) { console.error(err) }
        res.send(JSON.stringify(docs))
    })
})

Or, if you prefer, you can also use the Promise object that is returned by MongoClient if it is called without a callback argument:

var conn = MongoClient.connect('mongodb://localhost:27017/') // returns a Promise

app.get('/', function(req, res) {
    conn.then(client=> client.db('test').collection('test').find({}).toArray(function(err, docs) {
        if(err) { console.error(err) }
        res.send(JSON.stringify(docs))
    }))
})

Please note that I used the ES6 fat arrow function definition in the second example.

You are absolutely correct that you should not call MongoClient every time. Using a global variable or Promises allows the MongoDB node.js driver to create a connection pool, which achieves at least two good things:

  • Connections are reused in a pool, so there is no multiple expensive setup/teardown process for the lifetime of your application. You connect once, and let the driver take care of the rest for you.
  • You can control the amount of connection your application makes into the database, by limiting the size of the connection pool.

Edit 2018-08-24: The MongoClient.connect() method in node.js driver version 3.0 and newer returns a client object instead of a database object. The examples above were modified to keep it up to date with the latest node.js driver version.

Solution 2

I've written a tutorial on how to reuse mongodb connection in express. You can see here. Basically, it's a simple module, which you can use with expressjs like that:

var connection = require('./dbconnection');  
// url and optional config.
app.use(connection(app, 'mongourl', {});

And here's the code for the connection:

module.exports = function(app, uri, opts) {  
    if (typeof uri !== 'string') {
        throw new TypeError('Error: Unexpected mongodb connection url');
    }

    opts = opts || {};
    var property = opts.property || 'db';

    var connection;
    return function expressMongoDb(req, res, next) {
        if (!connection) {
            connection = MongoClient.connect(uri, opts);
        }

        connection
            .then(function (db) {
                req[property] = db;
                app.set('mongodb', db);
                next();
            })
            .catch(function (err) {
                connection = undefined;
                next(err);
            });
    };
};

Solution 3

You could use it this way:

that's server.js file:

import path from 'path'
import express from 'express'
import bodyParser from 'body-parser'
import morgan from 'morgan'
import db from './server/database'
import routes from './server/routes'

import webpack from 'webpack'
import webpackDevMiddleware from 'webpack-dev-middleware'
import webpackHotMiddleware from 'webpack-hot-middleware'
import webpackConfig from './config/webpack'
const app = express()
const port = process.env.PORT || process.env.NODE_PORT
const compiler = webpack(webpackConfig)

db(λ => {
  app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: webpackConfig.output.publicPath }))
  app.use(webpackHotMiddleware(compiler))
  app.use(morgan('dev'))
  app.use(bodyParser.json({ limit: '20mb' }))
  app.use(bodyParser.urlencoded({ limit: '20mb', extended: false }))
  app.use('/static', express.static('static'));
  //app.use('/api', jwt)
  app.use('/api', routes())

  app.set('json spaces', 2)

  app.get('*', function(request, response) {
    response.sendFile(path.resolve(__dirname, 'index.html'))
  })

  app.listen(port, (error) => {
    if (error) {
      console.error(error)
      throw error
    } else {
      console.info(`==>   Listening on port ${port}. Open up http://localhost:${port}/ in your browser.`)
    }
  })
})

server/database.js

import mongoose from 'mongoose'

export default callback => {
  const { MONGO_URL, MONGO_PORT, MONGO_DB } = process.env

  mongoose.connect(`mongodb://${MONGO_URL}:${MONGO_PORT}/${MONGO_DB}`, error => {
    if (error) {
      console.error('Please make sure your MongoDB configuration is correct and that service is running')
      throw error
    }
  })

  callback()
}

Then you'll have to define your mongoose models, for instance:

import mongoose, { Schema } from 'mongoose'

const ideaSchema = new Schema({
  title: {
    type: String,
    required: true
  },
  slug: {
    type: String,
    required: true,
    unique: true
  },
  description: {
    type: String,
    required: true
  }
})

export default mongoose.model('Idea', ideaSchema)

And just use controllers this way:

import HttpStatus from 'http-status-codes'
import mongoose from 'mongoose'
import sanitize from 'sanitize-html'
import slug from 'slug'
import Idea from '../models/idea'

const findAllIdeas = (req, res) => {
  Idea.find()
    .select('user title slug createdAt updatedAt')
    .populate({
      path: 'user',
      select: 'firstName lastName'
    })
    .then(data => res.status(HttpStatus.OK).json(data))
    .catch(error => res.status(HttpStatus.BAD_REQUEST).json(error))
}

export default { findAllIdeas, findIdeaBySlug, createIdea, addComment }

You will not have to connect to mongoDB for each get request.

So your route will look like that. Quite straightforward:

import { Router } from 'express'
import controller from '../controllers/idea'

const router = Router()

router.route('/')
  .get(controller.findAllIdeas)
  .post(controller.createIdea)

router.route('/:slug')
  .get(controller.findIdeaBySlug)

router.route('/comment')
  .post(controller.addComment)

export default router

Solution 4

My go-to code is as follows:

mongoose.connect(YOUR_URL ,function(err) {
  if (err) {
    console.log(err);
  }else{
    console.log("Connected to DB"); 
  }
});

Also try connecting to just localhost:27107, that may be your problem.