3

I'm new to Mongo. I needed a database for a simple project and ended up following a tutorial using Mongo with Monk but I have problems understanding how to handle errors.

Background: I have a registration form on the client side. When the user clicks a button, the data is sent via AJAX to the controller that (upon validation, but this is not relevant now) inserts such data into the database and sends back either success or error. When the db is up all seems to work fine.

The problem: If I don't start the db and try to send the request anyway, no error is returned. Simply nothing happens. After some time on the console I get: POST /members/addmember - - ms - -.

I think some error should be returned to the user in this case, so how could I do this?

The post request is below (pretty much as from the tutorial):

// app.js 

var db = monk('localhost:27017/dbname')
[...]
// I realize it might be not optimal here
app.use(function(req,res,next){ 
    req.db = db;
    next();
});

// members.js

router.post('/addmember', function(req, res) {
  var db = req.db;
  var collection = db.get('memberstest');
  collection.insert(req.body, function(err, result){
    res.json(
      (err === null) ? { msg: 'success' } : { msg: err }
    );
  });
});

If the db is down I guess the problem is actually even earlier than the insert, that is in that "db.get()". So how to check if that get can actually be done? I suppose that given the asynchronous nature of node something like a try/catch would be pointless here. Correct?

EDIT: After Neil's answer and a bit of trying, I put together the following that seems to do the job. However, given my scarce degree of confidence on this, I'd appreciate a comment if the code below works because it makes sense or by chance. I added the bufferMaxEntries: 0 options and modified the controller as follows. In the ajax callback I simply have an alert for now that shows the error message thrown (if any).

router.post('/addmember', async (req,res) => {

    try {
      let db = req.db;
      let collection = db.get('memberstest');

      collection.insert(req.body, function(err, result){
      res.json(
        (err === null) ? { msg: 'success' } : { msg: err }
      );
    });

    await db.then(() => 1);

    } catch(e) {
      res.json({msg: e.message})
    }

});
Tommy
  • 628
  • 11
  • 22
  • I guess your app can't work without active db connection, so you need just ensure that the app is connected to db, before starting listening the port. – alexmac Aug 09 '17 at 01:08
  • @alexmac Well yes, but I believe the Op's point ( and therefore question ) is that *"What to do if the database connection **goes away** during the lifecycle of the application?"*. Fortunately there are ways to handle that by configuring the underlying driver. – Neil Lunn Aug 09 '17 at 03:24

1 Answers1

1

Well you can actually set the bufferMaxEntries option ( documented under Db but deprecated for that object usage, use at "top level as demonstrated instead" ) on the connection, which essentially stops "queuing" requests on the driver when no connection is actually present.

As a minimal example:

index.js

const express = require('express'),
      morgan = require('morgan'),
      db = require('monk')('localhost/test',{ bufferMaxEntries: 0 }),
      app = express();

const routes = require('./routes');

app.use(morgan('combined'));

app.use((req,res,next) => {
  req.db = db;
  next();
});

app.use('/', routes);

(async function() {

  try {

    await db.then(() => 1);

    let collection = db.get('test');
    await collection.remove({});

    await collection.insert(Array(5).fill(1).map((e,i) => ({ a: i+1 })));
    console.log('inserted test data');

    await app.listen(3000,'0.0.0.0');
    console.log('App waiting');

  } catch(e) {
    console.error(e);
  }

})();

routes.js

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

router.get('/', async (req,res) => {
  try {
    let db = req.db,
        collection = db.get('test');

    let response = await collection.find();
    res.json(response);
  } catch(e) {
    res.status(500).json(e);
  }
});

module.exports = router;

So I am actually awaiting the database connection to at least be present on "start up" here, but really only for example since I want to insert some data to actually retrieve. It's not required, but the basic concept is to wait for the Promise to resolve:

await db.then(() => 1);

Kind of trivial, and not really required for your actual code. But I still think it's good practice.

The real test is done by stopping mongod or otherwise making the server unreachable and then issuing a request.

Since we set the connection options to { bufferMaxEntries: 0 } this means that immediately as you attempt to issue a command to the database, the failure will be returned if there is no actual connection present.

Of course when the database becomes available again, you won't get the error and the instructions will happen normally.

Without the option the default is to "en-queue" the operations until a connection is resolved and then the "buffer" is essentially "played".

You can simulate this ( as I did ) by "stopping" the mongod daemon and issuing requests. Then "starting" the daemon and issuing requests. It should simply return the caught error response.

NOTE: Not required, but in fact the whole purpose of async/await syntax is to make things like try..catch valid again, since you can actually scope as blocks rather than using Promise.catch() or err callback arguments to trap the errors. Same principles apply when either of those structures are actually in use though.

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
  • First of all thanks for taking the time to answer. I'm not sure I understand completely, but I'm trying to work around your example. One thing, I get an "unexpected identifier error" on let response = await collection.find(); Any idea why is that? – Tommy Aug 09 '17 at 04:16
  • Ok, I'm starting to have a feel of how this works. So what I have done is is to put an async function in my controller before the db.get() (maybe that's also why I was getting the error mentioned above). If that goes through, just move to the insert, otherwise send back the an error message to ajax and do something there. At this point, the code in app.js is simply for testing/log purposes when the app is started correct? I mean, I could technically get rid of it but it's best to have it there. Is that what you mean by good practice? Thanks again. – Tommy Aug 09 '17 at 05:10
  • @Tommy Been AFK for a bit. As I actually explained in the **NOTE** at the end, the `async/await` keywords which are available in modern nodejs releases ( default on from v8.x.x ) are not an actual requirement here. Everything can be done with either callbacks or promises as per your own desires and comfort. All that really matters here is the `bufferMaxEntries` in the connection options block. That's the only change to your existing code you actually need. You can of course grab a recent nodejs and simply run the sample code as is. But beyond the "options", nothing else here really matters. – Neil Lunn Aug 09 '17 at 05:28
  • Thanks! I managed to put together something that seems to be working, but given that still I really have no confidence on this, could you be so kind to have a look at my edited question to tell me if what I wrote makes sense or it works just by chance? :D Thanks again. (Ps. what does exactly the .then(()=>1) mean?) – Tommy Aug 09 '17 at 07:30
  • @Tommy Not sure how you keep missing this, but `db = require('monk')('localhost/test',{ bufferMaxEntries: 0 }),` is the **only** part that actually matters here. The rest of the code is just for "example" purposes. – Neil Lunn Aug 09 '17 at 07:32
  • Well, my mother dropped me when I was little. :D No kidding aside, well, actually if I remove that await statement I don't get any error returned if I shut the db down and send the post request.. So yeah besides being new to all this there must be something I still don't get. – Tommy Aug 09 '17 at 07:39
  • @Tommy I'm kind of running out of options for finding different ways to explain this. "My code here works unaltered under nodejs v.8.1.3", which means you simply drop it in a directory, install the three dependencies and just run it. "Your code" as **originally posted** need only change this `var db = monk('localhost:27017/dbname')` to this `var db = monk('localhost:27017/dbname',{ bufferMaxEntries: 0 })`. Then when you hang up the database it will error immediately when you try to access it. That's all you do. – Neil Lunn Aug 09 '17 at 07:49
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/151468/discussion-between-tommy-and-neil-lunn). – Tommy Aug 09 '17 at 08:30