How to Pass a Mongo DB Instance to Express Routes

Recently I’ve been working on a project with Express and Mongo and had found an interesting and fun way in which to structure the application around passing the Mongo instance using Express.

Within the application’s entry file, I connect to Mongo using the mongodb node package and it’s within the mongoClient.connect callback that I both start the Express server but also add my collection to the Express instance’s locals object – e.g., app.locals – making it more readily available to the controller.

Here is a working example of this pattern and some examples of the key modules involved:

Sample index.js:

const express = require('express');
const { MongoClient } = require('mongodb');
const { home } = require('./routes');
const { people } = require('./models');

const app = express();
const mongoClient = new MongoClient('mongodb://localhost:27017', {
  useUnifiedTopology: true,
});

app.use('/', home);

mongoClient.connect(async (connectionError, client) => {
  if (connectionError) {
    return console.error(connectionError);
  }
  try {
    const db = client.db('phonebook');
    const collections = await db.listCollections().toArray();
    const existing = collections.find(({ name }) => name === 'people');
    app.locals.people = existing
      ? db.collection('people', people)
      : await db.createCollection('people', people);
    return app.listen(3000, () => {
      console.log(`App listening at http://localhost:3000`);
    });
  } catch (error) {
    return console.error(error);
  }
});

Take note of app.locals.people assignment. Here I assign this key a value of the Mongo collection object returned by either the existing collection that was searched for, or by creating the collection if the collection did not already exist. This then allows the collection to be used within a controller, passed to each route as the callback function. I prefer this pattern as it is a bit more intuative than say, a singleton pattern for passing around the same database instance.

home.js route example:

const express = require('express');
const { getPeople } = require('../controllers/people');

const router = express.Router();
router.get('/', getPeople);

module.exports = router;

The controller, getPeople is a callback function passed as the second argument to router.get.

people.js controller example:

async function getPeople(req, res, next) {
  try {
    const { app: { locals: { people } } } = req;
    const documents = await people.find().toArray();
    return documents ? res.json(documents) : res.sendStatus(500);
  } catch (error) {
    return next(error);
  }
}

module.exports = {
  getPeople,
};

The controller function has the usual Express parameters: req, res, and next. Take note of some destructuring happening within the try block. It’s here with req.app.locals where the initial Mongo collection can be accessed for the controller to perform database functionality. This is the same object that was originally passed when the application was first initialized.

To see it in action, let’s add some test data using the Mongo CLI:

mongo
use phonebook
db.people.insert({ first: 'John', last: 'Foderaro', phone: '555-555-5555' })

Then, after starting the app and visiting http://localhost:3000, I receive a json response with the following:

[{"_id":"5f9377bae9b706c8e8dcc2b3","first":"John","last":"Foderaro","phone":"555-555-5555"}

🎉

For a full working example, please check out this repository on GitHub.

Like what you've read?