Mongodb find method can take dynamic query?

Viewed 91

I am a beginner in mongodb so would be great if someone advice me in how to write the below query efficiently.

I have a collection which has location and date as fields

There are 4 conditions for search

  • User can search without any parameter
  • User can search with date alone
  • User can search with location alone
  • user can search with both date and location

db.collection.find(query) --> Here this query object will change based on the request parameter.

  • If no req param , query = {}
  • If location present , query = {location query}
  • If date present , query = {datequery}
  • If both date and location query = {location query , datequery}

How i can make it simple with mongodb commands ? I know how to do it in SQL by adding whereclause based on the non null params in request but getting confused with nosql syntax.

3 Answers

You would build it like so:

const query = {};
if (req.params.location) {
    query.location = req.params.location
}
if (req.params.date) {
    query.date = new Date(req.params.date);
}
const results = await db.collection.find(query);

Where req.params is an object with location and date as optional fields.

You can build a query object.

const query = {};
req.params.location && (query.location = req.params.location)
req.params.date && (query.date = req.params.date)

The general problem you're facing here is the problem of building complex objects based on some parameters/inputs, which can be solved via a design pattern known as the builder pattern.

Though implementing such patterns can make your code look a bit complex (take this for example), here's my very simple implementation of it:

"use strict";

const queryBuilder = () => {
    const query = {};
    return {
        addLocation: function(location){
            if(typeof location === "string" && location.length>0){
                query.location = location.trim();
            }
            return this;
        },
        addDate: function(date){
            const dateObj = date ? new Date(date) : null;
            if(dateObj && !isNaN(dateObj.valueOf())){
                query.date = dateObj;
            }
            return this;
        },
        build: function(){
            Object.freeze(query);
            return query;
        }
    }
}

app.get('/:location/:date',async(req,res,next) => {

    //however you're connecting to the db, you'll need a connected instance
    const db = getDbSomehow();

    const myQuery = queryBuilder()
    .addLocation(req.params.location)
    .addDate(req.params.date)
    .build();

    const results = await db.collection.find(myQuery).toArray();

    //do whatever you need here


})

This looks like a lot more typing than some of the other answers that you've seen which must be working, but here are some advantages:

  1. Cleaner code in your request handler.
  2. You can shift all of the validations etc. in the methods of the builder.
  3. Need to add another property in the query? Implement the method for it in the builder, call it and you're done.
  4. Need to change the order of the keys in your query object? Change the order in which the builder's methods are called.
  5. Immutability: You can use Object.freeze() or a library like deep-freeze in your build method to make the object immutable so that nothing can change it once it's done.
Related