Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node.JS, Express & MongoDB :: Multiple Collections

I'm using Node.JS, Express, MongoDB and EJS. I have a question regarding multiple MongoDB collections under one database, and calling them to use on the front-end. I have 3 collections, and after much research haven't found how to call more than one collection without errors.

I understand that it wouldn't be a problem to store everything under one collection, but I feel like the best approach would be to separate them out by category. Here's my db/collection call:

app.use(express.bodyParser());
var MongoClient = require('mongodb').MongoClient;

app.get('/', function(req, res){
  MongoClient.connect("mongodb://localhost:27017/michael", function(err, db) {
    if(!err) {
      console.log("We are connected");
    }
    db.collection("portfolio", function(err, collection) {
      collection.find().sort({order_num: 1}).toArray(function(err, result) {
        var portfolio = [];
        if (err) {
          throw err;
        } else {
          for (i=0; i<result.length; i++) {
            portfolio[i] = result[i];
          }
          res.render('index.html', {portfolio: portfolio});
        }
      });
    });
  });
});

How could I make multiple calls to additional collections, such as an "about" collection, etc? I tried adding another...

db.collection("about", function(err, collection) {

...within...

MongoClient.connect("mongodb://localhost:27017/michael", function(err, db) {

...but it gives me errors in the Terminal console log.

Thanks in advance.

EDIT:

Here's what I'm trying to do, which gives me an error:

app.get('/', function(req, res){
  MongoClient.connect("mongodb://localhost:27017/michael", function(err, db) {
    if(!err) {
      console.log("We are connected");
    }
    db.collection("portfolio", function(err, collection) {
      collection.find().sort({order_num: 1}).toArray(function(err, result) {
        var portfolio = [];
        if (err) {
          throw err;
        } else {
          for (i=0; i<result.length; i++) {
            portfolio[i] = result[i];
          }
          res.render('index.html', {portfolio: portfolio});
        }
      });
    });
     // Here's the additional collection call:
     db.collection("about", function(err, collection) {
      collection.find().sort({order_num: 1}).toArray(function(err, result) {
        var about = [];
        if (err) {
          throw err;
        } else {
          for (i=0; i<result.length; i++) {
            about[i] = result[i];
          }
          res.render('index.html', {about: about});
        }
      });
    });
  });
});

Here's the error from the console:

ReferenceError: /Volumes/Files/WebDev/RHC/michael/michael/views/index.html:62
60|               <div class="row-fluid">
61|                 <ul id="work" class="thumbnails">
>> 62|                     <% for(i=0; i<portfolio.length; i++) { %>
63|                         <% if (portfolio[i].proj_type == "work") { %>
64|                             <% var newLine = ""; %>
65|                             <% var fancyBox = ""; %>

portfolio is not defined

So I have two collections in MongoDB under the db called "michael", and I'm trying to call both of them under one MongoClient.connect(). I'm then sending the results to the front-end (via EJS) in two arrays: "portfolio" and "about". It seems that when I do this, it renders "portfolio" undefined. Hope that makes sense.

like image 710
zickonezero Avatar asked Aug 18 '13 13:08

zickonezero


2 Answers

Ok, using aesede's solution plus my own tinkering, I got it to render two collections. It could probably use some finessing but here's what I got:

// GLOBAL ARRAYS FOR STORING COLLECTION DATA
var collectionOne = [];
var collectionTwo = [];
app.get('/', function(req, res){
  MongoClient.connect("mongodb://localhost:27017/michael", function(err, db) {
    if(!err) {
      console.log("We are connected");
    }
    db.collection("collectionOne", function(err, collection) {
      collection.find().sort({order_num: 1}).toArray(function(err, result) {
        if (err) {
          throw err;
        } else {
          for (i=0; i<result.length; i++) {
            collectionOne[i] = result[i];
          }
        }
      });
      db.collection("collectionTwo", function(err, collection) {
        collection.find().sort({order_num: 1}).toArray(function(err, result) {
          if (err) {
            throw err;
          } else {
            for (i=0; i<result.length; i++) {
              collectionTwo[i] = result[i];
            }
          }
        });
      });
      // Thank you aesede!
      res.render('index.html', {
        collectionOne: collectionOne,
        collectionTwo: collectionTwo
      });
    });
  });
});

The only bug, per se, that I found, was that when Node restarted and I hit "refresh" in the browser, I didn't see any content being rendered in the HTML. However, any subsequent refresh showed the content consistently.

like image 177
zickonezero Avatar answered Oct 26 '22 20:10

zickonezero


There is actually a really simple, intuitive answer to this problem. It's definitely not elegant and I do think an update to Express, Mongoose and NodeJS should include a preset way of doing this rather than having to logically think the process through.

All you have to do is nest your find() calls into each other for each collection you want to pass through to the page. Example below:

app.get("/", function(req, res) {
     Collection.find({}, function(err, collection) {
          if(err) {
               console.log(err);
          } else {
               Collection2.find({}, function(err, collection2) {
                    if(err) {
                         console.log(err)
                    } else {
                         res.render("page", {collection: collection, collection2: collection2});
                    }  
               }); 
          }
     });
});

You can keep doing this infinitely but obviously this concept does not scale. If you had thirty different collections to pass through, this would be a really inconvenient way to do so.

From the research I've done, this is not as simple as it should be because the Collection.find() method is actually a Promise function. If it wasn't a promise function, we could simply just run the find() method for each collection, pass the results into a global object, and pass the entire object through the render. You can't do this though because the global variable will be initialized and passed through the render before the promise function finishes. You end up passing an empty object through the render. I know this because I tried it.

I have no experience with Promise functions. I'm going to do some research on them and see if there is a way to structure your code to take advantage of the promise function and execute this task more "elegantly". But in the mean time, use my example above, as I believe it is the most intuitive way to accomplish this.

like image 24
Matthew Wolman Avatar answered Oct 26 '22 22:10

Matthew Wolman