Skip to main content

Sharing a MongoDB connection in Node.js/Express

4 min read

Older Article

This article was published 8 years ago. Some information may be outdated or no longer applicable.

Let’s talk about sharing a MongoDB connection properly in a Node.js/Express application.

Developers often wire up a RESTful API with these technologies and never look under the hood to check if the implementation actually performs well. The mistake usually hits the database hardest, especially when traffic climbs and requests pile up.

Incorrect implementation

Here’s an example of how not to do it:

const express = require('express');
const app = express();
const MongoClient = require('mongodb').MongoClient;
const ObjectId = require('mongodb').ObjectId;

const port = 3000;

const mongo_uri = 'mongodb://localhost:32768';

app.get('/', (req, res) => {
  MongoClient.connect(mongo_uri, { useNewUrlParser: true }).then((client) => {
    const db = client.db('my-db');
    const collection = db.collection('my-collection');
    collection
      .find({})
      .toArray()
      .then((response) => res.status(200).json(response))
      .catch((error) => console.error(error));
  });
});

app.get('/:id', (req, res) => {
  const id = new ObjectId(req.params.id);
  MongoClient.connect(mongo_uri, { useNewUrlParser: true }).then((client) => {
    const db = client.db('my-db');
    const collection = db.collection('my-collection');
    collection
      .findOne({ _id: id })
      .then((response) => res.status(200).json(response))
      .catch((error) => console.error(error));
  });
});

app.listen(port, () => console.info(`REST API running on port ${port}`));

We set up some variables and define two API endpoints. Each endpoint opens its own connection to the database and runs a query.

The problem: every endpoint call spawns a new connection. That won’t scale.

You can prove it. Start the API, hit both endpoints, then check the active connections in MongoDB’s CLI:

> db.serverStatus().connections;
{ "current" : 3, "available" : 838857, "totalCreated" : 37 }

Three connections: two for the API, one for the CLI itself. We should be sharing a single connection across the entire application.

Sharing the connection

There are several ways to fix this. Here’s one I find particularly clean. We’ll build the app around a simple idea: if the database is down, the API shouldn’t start. Makes sense. No point serving endpoints when there’s no data behind them.

We flip the logic. First, connect to the database. If that succeeds, spin up the API server.

MongoClient.connect(mongo_uri, { useNewUrlParser: true })
  .then((client) => {
    const db = client.db('my-db');
    const collection = db.collection('my-collection');
    app.listen(port, () => console.info(`REST API running on port ${port}`));
  })
  .catch((error) => console.error(error));

So far so good. But how do the routes access the connection? They don’t need to. Passing the collection reference is enough.

Express has a handy feature for sharing data between routes: app.locals. Attach properties to it, and they’re available inside any route definition:

// add this line before app.listen()
app.locals.collection = collection;

With the collection reference stored, we can rework the routes:

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

app.get('/:id', (req, res) => {
  const collection = req.app.locals.collection;
  const id = new ObjectId(req.params.id);
  collection
    .findOne({ _id: id })
    .then((response) => res.status(200).json(response))
    .catch((error) => console.error(error));
});

Check the connection count now:

> db.serverStatus().connections;
{ "current" : 2, "available" : 838858, "totalCreated" : 39 }

Two connections. One for the API, one for the CLI. That count won’t grow because we connect to the database once, at startup.

Connection Pooling

Sometimes the API returns data that doesn’t touch the database at all. In those cases, you might not want the whole API to die just because MongoDB is down. Only the database-dependent endpoints should fail.

Please note that by default the MongoDB Node.js API has a connection pool of 5 connections.

Here’s an example where we expand the connection pool and keep the API alive even if the database is unreachable:

// code excerpt
let db;
let collection;

MongoClient.connect(mongo_uri, { useNewUrlParser: true, poolSize: 10 })
  .then((client) => {
    db = client.db('my-db');
    collection = db.collection('my-collection');
  })
  .catch((error) => console.error(error));

app.get('/static', (req, res) => {
  res.status(200).json('Some static data');
});

app.get('/', (req, res) => {
  collection
    .find({})
    .toArray()
    .then((response) => res.status(200).json(response))
    .catch((error) => console.error(error));
});

Again, checking the connection count shows 2 connections.

Database disconnect

Best practice: don’t close database connections from Node.js during normal operation. If you need a different database, call the db() method to switch, and the existing connection pool handles it. That pool exists precisely to prevent blocking operations from freezing your Node.js process.

But when the application shuts down, you should disconnect. Here’s how:

// create an initial variable
let dbClient;
// assign the client from MongoClient
MongoClient.connect(mongo_uri, { useNewUrlParser: true, poolSize: 10 })
  .then((client) => {
    db = client.db('my-db');
    dbClient = client;
    collection = db.collection('my-collection');
  })
  .catch((error) => console.error(error));
// listen for the signal interruption (ctrl-c)
process.on('SIGINT', () => {
  dbClient.close();
  process.exit();
});

Conclusion

We’ve walked through several ways to handle MongoDB connections in a Node.js/Express application. Watch your connection management. Sloppy handling bleeds memory and drags performance down.