Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detecting Mongoose/MongoDB error in Express route instead of Mongoose.connect callback

A number of our systems running Express/Node.js have implemented a /healthcheck endpoint that our load balancers and other monitoring systems can watch to detect when one of our applications is in trouble. Basically the endpoint checks connections to all downstream dependencies the app has, including MongoDB.

This issue is that when the /healthcheck endpoint tries to run its test transaction and it fails, there's no obvious way to catch the error in the endpoint so that I can have it report that the MongoDB dependency has an issue. Instead the server throws a 500 error for the /healthcheck request (while still detectable as an issue, is not as helpful).

This code shows basically what we're doing. It's not complete and runnable, but Express users with Mongoose experience should recognize it easily.

const mongoose = require('mongoose')
const Schema = mongoose.Schema

const HealthcheckSchema = new Schema({
  widget: {
    type: String,
    required: true,
    unique: false,
  },
  createdAt: Date,
})

const HealthCheck = mongoose.model('Healthcheck', HealthcheckSchema, 'healthcheck')

const mongoCheck = () => Healthcheck.remove({}).exec()
  .then(() => Healthcheck.create({widget: 'xyzpdq'}))
  .then((results) => Healthcheck.findOne({widget: results.widget}).exec())
  .then(() => true)
  .catch(() => false)

const healthCheckEndpoint = (req, res) => {
  let status = {}

  Promise.all([mongoCheck()]) // normally there are other checks as well
    .then(function (values) {
      status.mongoUp = values[0]

      return res.status(200).json(status) // always return 200; the payload gives the health status, not the HTTP status code
    })
}

app.get('/healthcheck', healthCheckEndpoint)

In the main setup for the application, not specific to the healthcheck, we use mongoose.connect() with a callback. We have event handlers for various states like disconnected and error and such, and those events catch, but that doesn't help get the proper status in the /healthcheck endpoint.

I imagine I could use those event handlers to set state that the /healthcheck could pick up upon and return that, but I'd really prefer to make an actual query and use its success/failure as the result.

like image 329
Michael Oryl Avatar asked May 22 '20 14:05

Michael Oryl


1 Answers

This all turned out to be a red herring. The issue here was that there was an Express middleware layer that was making use of Mongo before the /healthcheck endpoint was reached.

In this case it was a Mongo session manager.

The solution was to exclude the /healthcheck URL from the session middleware.

function excludeSessionsForStaticURLs (req, res, next) {
  if (!new RegExp(/\/about|\/api\/heartbeat|\/healthcheck|\.css|\.js|\.jpg|\.png'/)
      .test(req.originalUrl)) {
    return sessionMiddleware(req, res, next)
  }

  return next()
}

app.use(excludeSessionsForStaticURLs)

So now that this exclusion has been put in place, the normal try/catch (async/await) and .catch() for Promises works and the status for Mongo is reflected as False when appropriate.

like image 113
Michael Oryl Avatar answered Nov 18 '22 20:11

Michael Oryl