Connecting and Working with MongoDB with Node & Express

Every web application needs a place to store its critical data. Node and Express apps are no exception. One of the most popular NoSQL databases in the last few years, especially when working with Node, is MongoDB.

As you grow, your needs grow. You need more than just to connect to your database and get some stuff. You need to add some business specific logic.

In this article we will look at how to connect to your MongoDB and then we will go all the way to building complex models which implement your business logic.

Installing

Before we begin you need to go to www.mongodb.org to download and run the latest version of Mongo. Follow the instructions and you will be ready in less than 5 minutes.

Once you have the database up and running, you will have to install the official Mongo driver to connect to it. It is a NPM package which provides useful methods for working with Mongo from Express and Node.

$ npm install mongodb

Now you are ready to begin.

Connecting

Let’s look at our first example.

var MongoClient = require('mongodb').MongoClient

var URL = 'mongodb://localhost:27017/mydatabase'

MongoClient.connect(URL, function(err, db) {
  if (err) return

  var collection = db.collection('foods')
  collection.insert({name: 'taco', tasty: true}, function(err, result) {
    collection.find({name: 'taco'}).toArray(function(err, docs) {
      console.log(docs[0])
      db.close()
    })
  })
})

This is a very simple example. It connects to Mongo, then it saves some data ({name: 'taco', tasty: true}) inside the foods collection and then it reads and displays the same data.

To run the code from above, you can put it in index.js and then do the following.

$ node index.js

If Mongo is running and you have installed the NPM package you should see as a result something like the line below.

{ "_id": 551e56b41143e39e7ff6b272, "name": "taco", "tasty": true }

If you run this file multiple times it will create many of the same objects inside the collection but with different _id each time.

For clarity, the example above has been kept intentionally simple. I’ve omitted all kind of error checks, like for example I don’t check whether the insert was a successful or even if reading the value produced a result.

Reusing the connection

Let’s go a little bit further. Let’s assume we have an app with the following structure

app/
  controllers/
    comments.js
    users.js
  models/
  views/
  app.js

For this example we will need to also install two more NPM packages.

$ npm install express jade

Express will serve our web app and jade will render its templates.

In file structure above, there are two controllers comments and users. The comments controller displays and edits comments, where as the other one does the same for users.

Both of them need to connect to the database. If you just copy the code from the previous example, inside each of these files, it will work but it won’t be very convenient.

When you later want to make a change to how you connect to Mongo, you will have to modify each file. The more files you have the more difficult and error prone it will be.

Instead, let’s create in our app folder a db.js file with the following content. It will help us manage our database connections and more.

var MongoClient = require('mongodb').MongoClient

var state = {
  db: null,
}

exports.connect = function(url, done) {
  if (state.db) return done()

  MongoClient.connect(url, function(err, db) {
    if (err) return done(err)
    state.db = db
    done()
  })
}

exports.get = function() {
  return state.db
}

exports.close = function(done) {
  if (state.db) {
    state.db.close(function(err, result) {
      state.db = null
      state.mode = null
      done(err)
    })
  }
}

This simple file will help us connect to the database when the app starting and then any controller can just use the the db object returned by the get method.

It will always be the same db object because require caches the result the first time it is called. Therefore it will return the same object, which will have the same get method, which in return will have access to the same state.db variable.

Let’s see how our app.js file will look.

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

var db = require('./db')

app.engine('jade', require('jade').__express)
app.set('view engine', 'jade')

app.use('/comments', require('./controllers/comments'))
app.use('/users', require('./controllers/users'))

// Connect to Mongo on start

db.connect('mongodb://localhost:27017/mydatabase', function(err) {
  if (err) {
    console.log('Unable to connect to Mongo.')
    process.exit(1)
  } else {
    app.listen(3000, function() {
      console.log('Listening on port 3000...')
    })
  }
})

This is the entry point of our application. It configures the app and then connects to the database.

Once the application is running it connects to Mongo so that other components can use the already established connections with the database.

For example if we want to display some comments, we can provide the following paths in the comments.js controller:

var express = require('express')
  , router = express.Router()

var db = require('../db')

router.get('/all', function(req, res) {
  var collection = db.get().collection('comments')

  collection.find().toArray(function(err, docs) {
    res.render('comments', {comments: docs})
  })
})

router.get('/recent', function(req, res) {
  var collection = db.get().collection('comments')

  collection.find().sort({'date': -1}).limit(100).toArray(function(err, docs) {
    res.render('comments', {comments: docs})
  })
})

module.exports = router

The file above provides two paths. The first one display all comments, where as the second one displays the hundred most recent comments sorted by date.

The other controller for the users can also reuse the database connection just as easily.

Just in the first example, this example is kept intentionally simple. There are few checks for errors. Also our db.js file contains just the bare functionality.

In a more complex application it can provide many more useful methods including some which will help test the application.

Building models with business logic

Reusing the connection for different controllers is a good thing, but we can further improve how we work with Mongo. For example, you may need to get all comments for many different tasks like displaying them or running some statistics on them or do something else.

With our current setup this will require that you copy the following code each time:

collection.find().toArray(function(err, docs) {
  // Do something...

})

Again, when you later want to make a change, you will have to make the change everywhere. It is the same problem as earlier.

But this is not the only one. The way it works now, requires that you controllers know database specific details.

To improve this lets go full Model-View-Controller pattern and implement some models.

app/
  controllers/
    comments.js
    users.js
  models/
    comments.js
    users.js
  views/
  app.js

As you can see we will have two more files in the models folder with content looking like the following

var db = require('../db')

exports.all = function(cb) {
  var collection = db.get().collection('comments')

  collection.find().toArray(function(err, docs) {
    cb(err, docs)
  })
}

exports.recent = function(cb) {
  var collection = db.get().collection('comments')

  collection.find().sort({'date': -1}).limit(100).toArray(function(err, docs) {
    cb(err, docs)
  })
}

Our comments model file uses the established database connection to request the data.

To use the model you will have to change a little bit in the controllers.

var express = require('express')
  , router = express.Router()

var Comments = require('../models/comments')

router.get('/all', function(req, res) {
  Comments.all(function(err, docs) {
    res.render('comments', {comments: docs})
  })
})

router.get('/recent', function(req, res) {
  Comments.recent(function(err, docs) {
    res.render('comments', {comments: docs})
  })
})

module.exports = router

At the end our models share the database management file db.js and they are the only ones who are aware how we are requesting our data from Mongo.

Next

We have just barely scratched the surface of how you can work with Mongo. For example, we have talked very little on how different errors should be handled and the code above is missing all error handling.


Other articles that you may like

Did you like this article?

Please share it

We are Stefan Fidanov & Vasil Lyutskanov. We share actionable advice about development with Node, Express, React and other web & mobile technologies.

It is everything that we have learned from years of experience working with customers from all over the world on projects of all sizes.

Let's work together
© 2024 Terlici Ltd · Terms · Privacy