Closing a Mongoose connection in a script which does not run forever

I have a JavaScript program which is supposed to run for a brief period of time, insert rows into a MongoDB database, and then exit. Here is the cut down version of the application:

var mongoose = require('mongoose');
var models = require('./models');

mongoose.connect('mongodb://localhost/test')
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function callback() {
var row = models('testschema')({
    name : 'test'
});

row.save(function (err, obj) {
    if (err) {
        console.log(err);
    } else {
        console.log('Saved item.');
    }
});

console.log('Closing DB');
db.close();
});

Now the above doesn't work properly, as the item never gets into the database. My feeling is that because save() is async, the db.close() is happening first and the item never gets saved. If I move the db.close() call into the callback for save, as so:

row.save(function (err, obj) {
    if (err) {
        console.log(err);
    } else {
        console.log('Saved meeting details');
    }
    console.log('Closing DB');
    db.close();
});

Then it works fine. However this isn't much practical help, as it means I can only write one row before needing to close the database. My question is, how do I close the Mongoose connection properly when I am in this situation:

var mongoose = require('mongoose');
var models = require('./models');

mongoose.connect('mongodb://localhost/test')
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function callback() {

    itemsToSave.forEach(function(item) {
        var row = models('testschema')({
            name : item.name
        });

        row.save(function (err, obj) {
            if (err) {
                console.log(err);
            } else {
                console.log('Saved meeting details');
            }

            // Can't do this here.
            //console.log('Closing DB');
            //db.close();
        });
    });

    // Nor here.
    //console.log('Closing DB');
    //db.close();
});

Edit: Here is the final version using C Blanchard's answer below. I should note, while it does achieve the desired result, I feel it has lost the convenience of mongoose at this point. If you are going to batch up calls to save() like this, you might as well take advantage of MongoDB's underlying bulk insert functionality and just use that to do the insert. I will probably do this task in another language as the async nature of node.js seems to make it nearly impossible to write elegant code to do something such as "open text file, for each line insert it into a database, close connection and exit". Anyhow, without further adieu, the final program:

var mongoose = require('mongoose');
var models = require('./models');
var async = require("async");


var rowsToSave = [];


var saveRow = function(item) {
return function (callback) {
    console.log('Saving meeting details');
    item.save(callback);
};
}

mongoose.connect('mongodb://localhost/test')
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.once('open', function callback() {

rowsToSave.push(saveRow(models('testschema')({ name: 'name1' })));
rowsToSave.push(saveRow(models('testschema')({ name: 'name2' })));
rowsToSave.push(saveRow(models('testschema')({ name: 'name3' })));
rowsToSave.push(saveRow(models('testschema')({ name: 'name4' })));
console.log(JSON.stringify(rowsToSave));

async.series(rowsToSave, function (err, res) {
    if (err) {
        console.log(err);
    } else {
        console.log('Saved meeting details');
    }

    console.log('Closing DB');
    db.close();
});
});

The other approach, which in some ways is nicer when looking at the code, but is also a horrible horrible hack to get around this deficiency, is to simply guesstimate the required time for the script and then close the db after this time has elapsed:

setTimeout(function() { db.close(); }, 5000);

I wouldn't blame anyone for doing this, MongoDB & Mongoose have forced you into a terrible position.

You can manage the control flow by using the async library. By using one of the control flow methods you can call the disconnect on Mongoose once all your saves are done.

What you could do is store all your save operations inside of an array and then pass it to async.series. async.series takes two arguments. 1) An array of functions to invoke in series. 2) A function which is invoked when all the functions in argument 1 are called.

Here's a sketch of a solution which follows the method described above:

// Bring in async library
var async = require("async");

// Create an array to store your save operations
var rowsToSave = [];

// Returns a function that will save a row when invoked
var saveRowMethod = function (item) {
  var row = models('testschema')({
    name : item.name
  });
  return function (callback) {
    row.save(callback);
  };
}

db.once('open', function callback() {
  itemsToSave.forEach(function(item) {
    // Store the row to save
    rowsToSave.push(saveRowMethod(item))
  });

  // This will invoke each save operation in rowsToSave (in series)
  async.series(rowsToSave, function (error, result) {
    if (error) {
      // Handle an error if one of the rows fails to save
    }
    console.log('Closing DB');
    db.close();
  })
});

Note that the functions you pass into rowsToSave must accept and invoke a callback when the save is complete. That's how async is able to track when the operations are done.

Update: I gave it more thought after your comment. Found a neater solution which relies only on Model#create - however Mongoose does not support bulk inserts for now (Issue #723)

Model#create accepts an array of objects and will do something similar to the solution I outlined above except it does not require async.

var rowsToSave = [];
rowsToSave.push({ name: 'name1' });
rowsToSave.push({ name: 'name2' });
rowsToSave.push({ name: 'name3' });
rowsToSave.push({ name: 'name4' });

TestModel.create(rowsToSave, function (err, name1, name2, name3, name4) {
  if (err) {
    console.log(err);
  } else {
    console.log('Saved meeting details');
  }

  console.log('Closing DB');
  db.close();
});