Counting documents with Mongoose

Counting documents with Mongoose
0

#1

Hey everyone, I am working on the URL shortener backend project and have decided to use mongoose to work with mongo db. I am new to mongoose, and I am having issues with a simple count of the documents which are already in my ‘urls’ collection (currently there are 2). Here is my code, the function below takes a string which is the req.params.url string:

const Url = require('../models/url');

var urlShortener = function(str){
  var patt = /^https?:\/\/[\w./]+/gi;
  var count;
  if(patt.test(str)){
    Url.find().exec(function(err, data){
      count = data.length;
    });
  return {num: count, url: str};
  }
}

module.exports = urlShortener;

Whenever I res.json the object which this function returns, the num field is blank and never shows a count of the objects currently in the database. Any ideas?


#2

Database queries are asynchronous so your return statement works before the content of exec function is executed.


#3

Ah, of course. Thank you! I have tried moving the return statement to within the call back, tried using the .then() keyword, removing .exec, and a lot of other things, but I am still having issues getting this code to return anything now. Any suggestions?


#4

So I got the code to work, but it only works in the routes file itself. I want to keep the logic separate and I don’t want my routes file to have a lot of logic in it. How do I do that a return the appropriate data while requiring another file in the routes file (in this case urlshortener.js)? Here is the code:

urlshortener.js(where I want my logic):
var Url = require(’…/models/url’);

var urlShortener = function(str){
  var patt = /^https?:\/\/[\w./]+/gi;
  var res_obj = {};
  var c;
  if(patt.test(str)){
Url.find({}).exec(function(err, urls){
  if(err){
    return err;
  }
  return urls;  **//this doesn't work**
});
  }
}

module.exports = urlShortener;

Here is my routes file (I don’t want to have to put logic in here):

var express = require('express');
var router = express.Router();
var urlshortener = require('./urlshortener/urlshortener');
const Url = require('./models/url');

router.get('/', function(req, res){
  res.status(200);
  res.sendFile(process.cwd() + '/urlshortener/index.html');
});

router.get('/new/:url(*)', function(req, res){
  var url = req.params.url;
  Url.find({}).exec(function(err, urls){  **//this works, but I don't want it here**
    if(err){
      res.send(err);
    }
    res.json(urls);
  });
  //res.json(urlshortener(url));  This doesn't work
});

// Respond not found to all the wrong routes
router.use(function(req, res, next){
  res.status(404);
  res.type('txt').send('Not found');
});

// Error Middleware
router.use(function(err, req, res, next) {
  if(err) {
    res.status(err.status || 500)
      .type('txt')
      .send(err.message || 'SERVER ERROR');
  }  
})


module.exports = router;

#5

I wouldn’t have thought to use exec here. Was always taught that you should have a really compelling reason and really good safe-guards (it can be used nefariously by those with more skill).

I’d recommend going straight to then. Something like this just worked for me:

app.get('/all', (req,res) => {
  Link.find().then((docs) => {
    console.log(docs.length);
    res.send('yay');
  });
});

#6

I do see Mongoose.js using exec in their docs. I’ve just never seen any tutorials/courses teach it that way.


#7

Try using callback function.
Something like

var urlShortener = function(str, callback){
  var patt = /^https?:\/\/[\w./]+/gi;
  var count;
  if(patt.test(str)){
    Url.find().exec(function(err, data){
      return callback(err, {num: data && data.length, url: str});
    });
  }
}

then use urlShortener like this

urlShortener(str, function(err, data) {
  // do something
})

#8

Why would you use then if you could just write
Link.find(function(err, data) {
// working code
}

I suggest you to read official documentation first


#9

Thanks for assuming I hadn’t read the documentation. Here’s the page for promises: http://mongoosejs.com/docs/promises.html

Mostly, I use then and promise-based coding as a way of keeping the code display a little flatter (easier to scan), chain-able (you can add additional then calls), and easier to maintain (thanks to the chaining feature, code becomes a little more modular).

It will also become more important as async/await become more prominent (just landed in Node8).

I did forget something though, @InfiniteSet . You have to tell Mongoose that you prefer promises over callbacks.

Just above your connect call, you should have something like:

mongoose.Promise = global.Promise;
mongoose.connect(process.env.MONGODB_URI, {
  useMongoClient: true
});

That worked for me (https://github.com/cloudsociety/fCC-shortlink-microservice). If you prefer callbacks, that’s cool too. There are always multiple ways to get the job done. :grinning:


#10

If you read it you should have noticed it uses syntax simular to what topicstarter uses. And if you use promises you need to add catch in case there is an error during query execution.


#11

Yes, you are absolutely right. I didn’t add an error block or catch block in my EXAMPLE. Now, before you continue to attack me for no reason, go and look at my repo. You’ll see that I always include an error block in my promises.

And yes, the OP certainly used promises. I didn’t question that. My concern was the use of exec when used with input that isn’t sanitized. By using exec in that way, the code could be compromised. And, as shown in my example, it works without exec.

Use promises or use a callback. There is no “best” answer to that part, but avoid exec when the input isn’t in your control.


Doh! eval. Don’t use eval. Somehow got those mixed up (they do sound like similar operations). I’m wrong there–exec is specific to Mongoose in this case.

I’ve never used it. Probably never had need because of promises.

Again, my bad for mixing those two up. Gonna go take my foot out of my mouth now.


#12

Jeez nobody’s attacking you. It’s just not clear to me what’s the problem with exec function and what it has to do with code compromising. Yes in this particular instance there are no chainable functions so there is no particular need for exec but it will work the same with or without it.

Can you elaborate on that?


#13

Good on you. Keep your routes clean. Mongoose makes this very easy simply by virtue of implementing query objects as promises. You can have a function that hides the Mongoose logic and returns a promise. I usually keep all of this logic in a file called ‘Model.js’, but you can call it whatever or put it wherever you like.

const Url = require('./models/url');

function findUrl(url) {
    return Url.find(url).exec(); // exec() isn't strictly necessary, but it's the only way to get a full promise in Mongoose
}

module.exports = findUrl;

Then, in your routes file…

const findUrl = require('../Model.js');
// other stuff

router.get('/new/:url(*)', function(req, res) {
    const url = req.params.url;
    findUrl(url)
        .then(function(response) {
             res.json(response); // ... or whatevs
        })
        .catch(function(error) {
            //explode
        });
});

This is especially handy when you have much more complicated queries that require multiple models or some processing after you’ve got the data from the DB.


#14
function findUrl(url) {
    return Url.find(url).exec(); // exec() isn't strictly necessary, but it's the only way to get a full promise in Mongoose
}

Thank you so much for this code snippet! It is already helping me make my code more modular!