Sonson123
Sonson123

Reputation: 11447

Error handling with Mongoose

I am an absolute NodeJS beginner and want to create a simple REST-Webservice with Express and Mongoose.

Whats the best practice to handle errors of Mongoose in one central place?

When anywhere an database error occurs I want to return a Http-500-Error-Page with an error message:

if(error) {
  res.writeHead(500, {'Content-Type': 'application/json'});
  res.write('{error: "' + error + '"}');
  res.end();
}

In the old tutorial http://blog-next-stage.learnboost.com/mongoose/ I read about an global error listener:

Mongoose.addListener('error',function(errObj,scope_of_error));

But this doesn't seem to work and I cannot find something in the official Mongoose documentation about this listener. Have I check for errors after every Mongo request?

Upvotes: 34

Views: 42668

Answers (3)

TOPKAT
TOPKAT

Reputation: 8688

Simpler and more up to date solution IMO:

Connection errors

Handle connection level errors when you instantiate your db:

const mongooseConnection = mongoose.createConnection(databaseURL)

mongooseConnection.on('error', err => {
  throw new Error('Mongo database connexion error')
})

Query level errors

Query level errors may appear if you write fields with wrong types or use query options (populate, sort...) incorrectly. We can use a more readable/up to date try catch await over the old promise.callback.catch syntax (mongoose doc)

try {
  await Band.findOne({ _id: badId }).exec();
} catch (err) {
  throw new Error('Mongo database connexion error')
}

BONUS: advices and good practices

=> Use an error handler across all your app

Error handling in the 2 example above are not very relevant because you loose all contextual informations (mongo error msg, stack trace, database name, userId...)

You may handle errors in a centralized way so that you can pass contextual informations alongside the error message like so:

try { ... } catch (err) {
  throw ApplicationError(
    'myMessage',
    {
      // Here we pass additional informations
      // this will help handling the error on the express api side
      httpCode: err instanceof mongoose.Error.ValidationError ? 422 : 500,
      // with this one you can display original error messsage/stack trace
      originalError: err, 
      // other contextual informations may be useful to display
      // but be carreful not to display sensitive informations
      // here (Eg DB connection string)
      databaseName,
      userId,
      userRole,
      methodName,
    }
  )
}

class ApplicationError extends Error {
  constructor(errMsg, additionalInfos) {
    // Here handle error as you want
  }
}

List of all error types thrown by mongoose

=> Use a request handler to handle all mongoose queries the same way

A common pattern to avoid duplicating code is to create a function to handle the execution of mongoose promise for you:

async function afterRequest(mongoosePromise, { sort, page, limit }) {
  try {
    if (sort) promise.sort(sort)

    // PAGINATION
    if (page) promise.skip(localConfig.page * limit || 25).limit(limit || 25)
    else if (limit) promise.limit(limit)

    return await promise.exec()

  } catch (err) { /** handle error like in the above code */ }
}

So we can now use it like:

const mongoosePromise = Band.find()
afterRequest(mongoosePromise, { page: 1, limit: 10 })

This pattern has the advantage of:

  • handling all in the same place, so it's the safest place to add custom code for security handling or validation for example
  • easily exposing some options like pagination... to api or some services that you may not want to deal directly with mongoose requests

The above is juste a very simplified example, but to go further, here are some features we could implement:

  • check if the user has the permission to populate
  • masking certain fields in filter or updated/created fields related to user permissions (a simple user may not been able to write his permissions for example)
  • force certain filters (companyAdmin may have access only to their company so we want to enforce filter { companyId: user.companyId }
  • ...

Upvotes: 2

aaronheckmann
aaronheckmann

Reputation: 10780

If you're using Express, errors are typically handled either directly in your route or within an api built on top of mongoose, forwarding the error along to next.

app.get('/tickets', function (req, res, next) {
  PlaneTickets.find({}, function (err, tickets) {
    if (err) return next(err);
    // or if no tickets are found maybe
    if (0 === tickets.length) return next(new NotFoundError));
    ...
  })
})

The NotFoundError could be sniffed in your error handler middleware to provide customized messaging.

Some abstraction is possible but you'll still require access to the next method in order to pass the error down the route chain.

PlaneTickets.search(term, next, function (tickets) {
    // i don't like this b/c it hides whats going on and changes the (err, result) callback convention of node
})

As for centrally handling mongoose errors, theres not really one place to handle em all. Errors can be handled at several different levels:

connection errors are emitted on the connection your models are using, so

mongoose.connect(..);
mongoose.connection.on('error', handler);

// or if using separate connections
var conn = mongoose.createConnection(..);
conn.on('error', handler);

For typical queries/updates/removes the error is passed to your callback.

PlaneTickets.find({..}, function (err, tickets) {
    if (err) ...

If you don't pass a callback the error is emitted on the Model if you are listening for it:

PlaneTickets.on('error', handler); // note the loss of access to the `next` method from the request!
ticket.save(); // no callback passed

If you do not pass a callback and are not listening to errors at the model level they will be emitted on the models connection.

The key take-away here is that you want access to next somehow to pass the error along.

Upvotes: 48

George Prethesh
George Prethesh

Reputation: 18

hey this is the simplest way i found..

try { } catch (error) {
    console.log(error);
    // checking validation
    if (error.name === "ValidationError") {
        
        const message = Object.values(error.errors).map(value => value.message);
        return res.status(400).json({
            error: message
        })
    }
    res.status(400).json(error.message)
}

}

just copy paste

Upvotes: 0

Related Questions