Giter VIP home page Giter VIP logo

mongo-models's Introduction

mongo-models

JavaScript class interfaces to MongoDB collections.

Build Status Dependency Status devDependency Status peerDependency Status

MongoDB's native driver for Node.js is pretty good. We just want a little sugar on top.

Mongoose is awesome, and big. It's built on top of MongoDB's native Node.js driver. It's a real deal ODM with tons of features. You should check it out.

We wanted something in between the MongoDB driver and Mongoose. A light weight abstraction where we can interact with collections via JavaScript classes and get document results as instances of those classes.

We're also big fans of the object schema validation library joi. Joi works well for defining a model's data schema.

API reference

https://github.com/jedireza/mongo-models/blob/master/API.md

See the old v1.x API reference.

Install

$ npm install mongo-models

Usage

Creating models

You extend the MongoModels class to create new model classes that map to MongoDB collections.

Let's create a Customer model.

'use strict';
const Joi = require('joi');
const MongoModels = require('mongo-models');

const schema = Joi.object({
    _id: Joi.object(),
    name: Joi.string().required(),
    email: Joi.string().email(),
    phone: Joi.string()
});

class Customer extends MongoModels {
    static create(name, email, phone) {

        const document = new Customer({
            name,
            email,
            phone
        });

        return this.insertOne(document);
    }

    speak() {

        console.log(`${this.name}: call me at ${this.phone}.`);
    }
}

Customer.collectionName = 'customers'; // the mongodb collection name
Customer.schema = schema;

module.exports = Customer;

Example

'use strict';
const BodyParser = require('body-parser');
const Customer = require('./customer');
const Express = require('express');
const MongoModels = require('mongo-models');

const app = Express();
const connection = {
    uri: process.env.MONGODB_URI,
    db: process.env.MONGODB_NAME
};

app.use(BodyParser.json());

app.post('/customers', async (req, res) => {

    const name = req.body.name;
    const email = req.body.email;
    const phone = req.body.phone;
    let customers;

    try {
        customers = await Customer.create(name, email, phone);
    }
    catch (err) {
        res.status(500).json({ error: 'something blew up' });
        return;
    }

    res.json(customers[0]);
});

app.get('/customers', async (req, res) => {

    const name = req.query.name;
    const filter = {};

    if (name) {
        filter.name = name;
    }

    let customers;

    try {
        customers = await Customer.find(filter);
    }
    catch (err) {
        res.status(500).json({ error: 'something blew up' });
        return;
    }

    res.json(customers);
});

const main = async function () {

    await MongoModels.connect(connection, {});

    console.log('Models are now connected.');

    await app.listen(process.env.PORT);

    console.log(`Server is running on port ${process.env.PORT}`);
};

main();

Run the example

To run the example, first clone this repo and install the dependencies.

$ git clone https://github.com/jedireza/mongo-models.git
$ cd mongo-models
$ npm install

The example is a simple Express API that uses the Customer model we created above. View the code.

$ npm run example

Have a question?

Any issues or questions (no matter how basic), open an issue. Please take the initiative to read relevant documentation and be pro-active with debugging.

Want to contribute?

Contributions are welcome. If you're changing something non-trivial, you may want to submit an issue before creating a large pull request.

License

MIT

Don't forget

What you create with mongo-models is more important than mongo-models.

mongo-models's People

Contributors

blaircooper avatar henriquesa avatar jedireza avatar maratvmk avatar nickrobillard avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

mongo-models's Issues

Promise support

Hi,

I have seen frame and aqua (your projects), mongo-models supports "Promise" to use async-await syntax sugar?

Thank you!!

Mongo Lookup Support

MongoDB lookup commands do not support lookup with object id and a string id. Reference ticket. Most of my other collections have a userId field that is a string ID, and maybe others do too. I created a function to do something similar.

I can create tests and then a pull request, if interested. Also intrested in adding a pagedFind Lookup as well because that would help me more in my porjects.
Lookup By Id Function

Usage

User Collection:

[
  {
    "_id": "ObjectId(59d519e18b3aca4544fed939)",
    "username": "testUser1",
    "email": "[email protected]"
  },
  {
    "_id": "ObjectId(57e50b2e14096f000334a4b7)",
    "username": "testUser2",
    "email": "[email protected]"
  }
]

Example Collection (Mood):

Schema:

{
  "_id": "ObjectId",
  "mood": "string",
  "userId": "string",
  "timeCreated": "date"
}

Collection:

[
  {
    "_id": "ObjectId(57e2a2dc71c112000321d767)",
    "mood": "happy",
    "userId": "59d519e18b3aca4544fed939",
    "timeCreated": "2017-10-04 13:26:57.669"
  },
  {
    "_id": "ObjectId(57e0c26a5b903e0003a131ec)",
    "mood": "sad",
    "userId": "59d519e18b3aca4544fed939",
    "timeCreated": "2017-10-05 13:26:57.669"
  },
  {
    "_id": "ObjectId(57e0b0e95b903e0003a131e0)",
    "mood": "sad",
    "userId": "57e50b2e14096f000334a4b7",
    "timeCreated": "2017-10-06 13:26:57.669"
  },
  {
    "_id": "ObjectId(57e071ab61317900039159be)",
    "mood": "sad",
    "userId": "57e50b2e14096f000334a4b7",
    "timeCreated": "2017-10-07 13:26:57.669"
  }
]

Function

Mood.joinById(filter, ForeignCollection, joinField, localField,callback);

Example

const Mood = require('./models/mood');
const User = require('./models/user');

Mood.joinById({}, User, 'userId', 'user', callback);

returns

[
  {
    "_id": "ObjectId(57e2a2dc71c112000321d767)",
    "mood": "happy",
    "userId": "59d519e18b3aca4544fed939",
    "timeCreated": "2017-10-04 13:26:57.669",
    "user": [
      {
        "_id": "ObjectId(59d519e18b3aca4544fed939)",
        "username": "testUser1",
        "email": "[email protected]"
      }
    ]
  },
  {
    "_id": "ObjectId(57e0c26a5b903e0003a131ec)",
    "mood": "sad",
    "userId": "59d519e18b3aca4544fed939",
    "timeCreated": "2017-10-05 13:26:57.669",
    "user": [
      {
        "_id": "ObjectId(59d519e18b3aca4544fed939)",
        "username": "testUser1",
        "email": "[email protected]"
      }
    ]
  },
  {
    "_id": "ObjectId(57e0b0e95b903e0003a131e0)",
    "mood": "sad",
    "userId": "57e50b2e14096f000334a4b7",
    "timeCreated": "2017-10-06 13:26:57.669",
    "user": [
      {
        "_id": "ObjectId(57e50b2e14096f000334a4b7)",
        "username": "testUser2",
        "email": "[email protected]"
      }
    ]
  },
  {
    "_id": "ObjectId(57e071ab61317900039159be)",
    "mood": "sad",
    "userId": "57e50b2e14096f000334a4b7",
    "timeCreated": "2017-10-07 13:26:57.669",
    "user": [
      {
        "_id": "ObjectId(57e50b2e14096f000334a4b7)",
        "username": "testUser2",
        "email": "[email protected]"
      }
    ]
  }
]

you can also add filters and options like this:

const Mood = require('./models/mood');
const User = require('./models/user');

const moodFields = User.fieldsAdapter('mood') //joinField is added in be default as well
const moodOptions = null //must have null for correct arguments
const userFields = User.fieldsAdapter('username')
//not need for userOptions if null

Mood.joinById({}, User, 'userId', 'user', moodFields, moodOptions, userFields,callback);

returns

[
  {
    "_id": "ObjectId(57e2a2dc71c112000321d767)",
    "mood": "happy",
    "userId": "59d519e18b3aca4544fed939",
    "user": [
      {
        "_id": "ObjectId(59d519e18b3aca4544fed939)",
        "username": "testUser1"
      }
    ]
  },
  {
    "_id": "ObjectId(57e0c26a5b903e0003a131ec)",
    "mood": "sad",
    "userId": "59d519e18b3aca4544fed939",
    "user": [
      {
        "_id": "ObjectId(59d519e18b3aca4544fed939)",
        "username": "testUser1"
      }
    ]
  },
  {
    "_id": "ObjectId(57e0b0e95b903e0003a131e0)",
    "mood": "sad",
    "userId": "57e50b2e14096f000334a4b7",
    "user": [
      {
        "_id": "ObjectId(57e50b2e14096f000334a4b7)",
        "username": "testUser2"
      }
    ]
  },
  {
    "_id": "ObjectId(57e071ab61317900039159be)",
    "mood": "sad",
    "userId": "57e50b2e14096f000334a4b7",
    "user": [
      {
        "_id": "ObjectId(57e50b2e14096f000334a4b7)",
        "username": "testUser2"
      }
    ]
  }
]

v2.x.x Release Notes

2.x.x is a significant change to the API. We've moved away from callbacks and are exclusively using async/await. The most important changes to be aware of are:

  • The minimum supported driver is [email protected]
  • Callbacks are gone everything is async/await.
  • The property constructWithSchema has been removed. The default behavior is to always construct with the schema.
  • The constructor will throw if there was a validation error.
  • The property collection has been renamed to collectionName.
  • The connect method now expects a connection object with keys uri and db.
  • The pagedFind method no longer accepts the fields or sort arguments. Use the options argument instead.
  • For some methods that pulled information out of the result documents (like the count of records affected) now simply return the result document from the MongoDB driver. Review your usage of: deleteMany, deleteOne, replaceOne, updateMany, updateOne.

It's very possible that I've not mentioned something important that changed. Please mention anything you think should be clarified.

Using dynamic schema

Hi there,

Thank for your great lib, one question here is how can i use dynamic schema? I want to use some important fields for indexing and set the constrain for the data otherwise i want to use remain as dynamic fields?
Is that possible?

Thanks.

[Question] Models schema validation breaks partial fields loading

For instance, I have schema of Device where field passcode is Joi.string.require(). Now I want to get objects from corresponding collection but do not need all the fields. Let's say I want only get _id, name, lastSeen. So I am calling it like:

await Device.find(someQuery, Device.fieldAdapter('_id name lastSeen'))

This call throws an exception with Joi validation message child "passcode" fails because ["passcode" is required].

Looks like bug for me.

Extending models

Hi @jedireza

Is there a way to extend and make new models from already constructed MongoModels? For example I already have a User Model, and now need to specialize the user and extend User to say, Professor (which would have it's own schema additions over user).

Is that possible currently? I tried to read through documentation but did not find anything in this regard.

Thanks in advance!

Add mongodb to dependencies

Any reason why mongodb is not in the actual dependencies? I am using the hapi module and don't think I should have to manually install mongo-models and mongodb

[Question] Hoek dependency

First of all thank you for mongo-models package, I like it how it slim and thin. I checked its code and found that it is actually don't need hoek dependency as only applyToDefauls used that can be easily replaced by Object.assign(...). Do you mind if I prepare PR that allows to reduce dependencies count by one?

Cannot process the result in resultFactory()

When I use mongo-models with electrode, the find function will give this exception

TypeError: Class constructor MongoModels cannot be invoked without 'new'

in the place

   if (Object.prototype.toString.call(result) === '[object Array]') {
      result.forEach((item, index) => {
        console.log('...... seeeefff');
        console.log(item); // have value 
        result[index] = new self(item); // exception in here 
      });
    }

I am not so sure this is because of babel transformation or not. This is my .babelrc

{
  	"presets": [ "stage-0" ], 
        "extends": "./node_modules/electrode-archetype-react-app/config/babel/.babelrc",
   	"plugins": [
	    ["babel-root-import"],
	    ["transform-runtime", {
	        "polyfill": false,
	        "regenerator": true
		}]
  	]
}

options

Hi There,
Im working with your API and im trying to call another collection from another folder, but i receive an error with the form "Cannot read property 'collection' of undefined". But I know collection is fine because Im using it from another API.

So I tried to use the mongo connect, and here is my question because it doesnt appear anything on the documentation.

What are the options for the connect method....

Thanks for your time :))

Greetings from Chile

MongoClient.connect function incompatible with Node MongoDB 3.6 driver

Hello!

The MongoClient.connect method in the MongoDB 3.6 driver has changed. See the changelog for more info, beginning with "MongoClient.connect works as expected but it returns...". MongoClient.connect no longer returns the database, but an instance of MongoClient.

So the connect method will have to be refactored to something like the following:

static connect(uri, dbName options, callback) { 
        Mongodb.MongoClient.connect(uri, options, (err, client) => {
            if (err) {
                return callback(err);
            }
            MongoModels.db = client.db(dbName);
            callback(null, db);
        });
 }

Or maybe the dbName could be included in the options object.

Happy to do a pull request.

Support for other WriteResult parameters

Write operations like update, updateOne, replaceOne, etc. often have more results than modifiedCount. In my case, I'd like to access matchedCount to see if my filter actually got any hits. What do you think about returning to these callbacks not only modifiedCount, but as a third parameter the raw operation result so we can access these? The callback would use the signature function (error, count, results) in these cases.

I'm able to open a PR with updated testing and documentation if you think that's a good idea.

[Question] createIndexes guide

Hi,

Thank for your great work. I just wonder what is the best way to use createIndexes?

Can it be totally automatic when we defined model?

Cheers.

Native Async/Await Support

Hello,

I am not sure if there is an open issue on this or if I am doing something wrong, but I do not beleive this library properly complies with the NodeJS native async protocol:

Example:

const Goat = require("models/Goat");

async someFunction(){

   var allGoats = await Goat.find({});
   console.log(allGoats); // This returns undefined;


   var nativeGoats = await db.collection("Goats").find({}).asArray();
   console.log(nativeGoats); // Get an [] of all my goats

}

I did some tweaking on my own fork, and found that I could get it working, but I do not know how this plays with compatibility with other versions:

    static resultFactory() {

        const args = new Array(arguments.length);
        for (let i = 0; i < args.length; ++i) {
            args[i] = arguments[i];
        }

        var first = args.shift();
        var next = first,
            resolve = false,
            reject = false;
        if (first && first.forEach && first.length == 4) {
            var [next, resolve, reject] = first;
        }
        if (typeof next !== 'function' || !next || !next.apply) next = false;

        const err = args.shift();
        let result = args.shift();

        if (err) {
            args.unshift(result);
            args.unshift(err);
            if (reject) reject(err);
            if (next) return next.apply(undefined, args);
            return;
        }

        const self = this;

        if (Object.prototype.toString.call(result) === '[object Array]') {
            result.forEach((item, index) => {

                result[index] = new self(item);
            });
        }

        if (Object.prototype.toString.call(result) === '[object Object]') {
            if (result.hasOwnProperty('value') && !result.hasOwnProperty('_id')) {
                if (result.value) {
                    result = new this(result.value);
                } else {
                    result = undefined;
                }
            } else if (result.hasOwnProperty('ops')) {
                result.ops.forEach((item, index) => {

                    result.ops[index] = new self(item);
                });

                result = result.ops;
            } else if (result.hasOwnProperty('_id')) {
                result = new this(result);
            }
        }

        args.unshift(result);
        args.unshift(err);
        if (resolve) resolve(result);
        if (next) next.apply(undefined, args);
    }

โ€ฆ

    static async find() {

        const args = new Array(arguments.length);
        for (let i = 0; i < args.length; ++i) {
            args[i] = arguments[i];
        }

        return new Promise((resolve, reject) => {
            const collection = MongoModels.db.collection(this.collection);

            var cb = false;
            if (typeof args[args.length - 1] == 'function') cb = args.pop();
            const callback = this.resultFactory.bind(this, [cb, resolve, reject, true]);

            collection.find.apply(collection, args).toArray(callback);

        });
    }

Any thoughts on this, feel free to take a peek at my fork, but I am not sure how you would implement this with compatibility.

Nick

Support Promise

Currently, the result needs to be processed through callback. Is there any plan to support Promise in the near future to leverage the async/await ?

Problem with babel

if we use babel we got this error : (i convert this class to es5 and works, i think you missed something)

/home/amir/Desktop/NodeJs/cannal/node_modules/mongodb/lib/utils.js:123
    process.nextTick(function() { throw err; });
                                  ^

TypeError: Class constructor MongoModels cannot be invoked without 'new'
    at new Session (/home/amir/Desktop/NodeJs/cannal/server/models/session.js:23:103)
    at Function.resultFactory (/home/amir/Desktop/NodeJs/cannal/node_modules/mongo-models/index.js:119:26)
    at handleCallback (/home/amir/Desktop/NodeJs/cannal/node_modules/mongodb/lib/utils.js:120:56)
    at /home/amir/Desktop/NodeJs/cannal/node_modules/mongodb/lib/collection.js:1416:5
    at handleCallback (/home/amir/Desktop/NodeJs/cannal/node_modules/mongodb/lib/utils.js:120:56)
    at /home/amir/Desktop/NodeJs/cannal/node_modules/mongodb/lib/cursor.js:683:5
    at handleCallback (/home/amir/Desktop/NodeJs/cannal/node_modules/mongodb/node_modules/mongodb-core/lib/cursor.js:171:5)
    at nextFunction (/home/amir/Desktop/NodeJs/cannal/node_modules/mongodb/node_modules/mongodb-core/lib/cursor.js:689:5)
    at /home/amir/Desktop/NodeJs/cannal/node_modules/mongodb/node_modules/mongodb-core/lib/cursor.js:600:7
    at queryCallback (/home/amir/Desktop/NodeJs/cannal/node_modules/mongodb/node_modules/mongodb-core/lib/cursor.js:232:18)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! [email protected] babel-node: `babel-node --presets=es2015 --inspect --debug "./server.js"`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the [email protected] babel-node script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     /home/amir/.npm/_logs/2017-11-19T15_24_35_238Z-debug.log

Would you consider an alternative?

I've been using mongo-models for a long time but I know it's been hard to keep up with all the changes to Mongodb. I've seen some of the updates to the README about not supporting it any more. But it's been a pattern that I appreciate and so I created this:

@enciv/mongo-collections

It's not exactly compatible with mongo-models but migration would be strait forward. And people could continue using the schemas and validation pattern of mongo-models, or they could migrate to the schemas of mongodb.

The main implementation difference is that in mongo-collections the Collection class actively copies all the properties of the Mongodb Collection instance, after the connection to the server is made - so it 'inherits' all the methods of a collection.

I would appreciate any feedback/questions/suggestions on it.

Collection global prefix

not sure where would be best place to implement (here, hapi-mongo, frame, aqua)....

but it would be nice to be able to set an env/config to have a global collection prefix.

example:

// model
Kitten.collection = 'kittens';

//aqua config
hapiMongoModels: {
        mongodb: {
            uri: {
                $filter: 'env',
                production: process.env.MONGODB_URI,
                test: 'mongodb://devurl',
                $default: 'mongodb://devurl'
            }
        },
        autoIndex: true,
        prefix: 'aqua_'
    };

result collection would be 'aqua_kittens'

Proposal: Add an option that allows one to not validate mongo query results

Disclaimer

I have used mongo-models in several projects now and I really like it. At this point, I feel (and hope) like I have a pretty good understanding of its intended purpose. As your readme clearly states mongo-models is "A light weight abstraction where we can interact with collections via JavaScript classes and get document results as instances of those classes". So, data passed in via the constructor as well as mongo results are an instance of mongo-models. And thus, they are both subject to validation.

The Problem

Most of my projects have existing or migrated data sets, and as one would expect, my mongo-models schema isn't followed perfectly in this existing data set. This means that even find methods implemented in mongo-models (and all methods that pass results through this.resultFactory) can potentially fail validation and throw a ValidationError. The first time I noticed this happening when doing a find operation, I was very surprised. It wasn't intuitive to me that existing data would be validated (I was used to Mongoose behavior). In these situations, it's usually not very feasible to fix my entire data set to match my schema. So I've avoided using find methods and used direct mongo queries (while still using other features of mongo-models). Of course work arounds in general are not ideal, but also, for other devs who work on the same code later, it is not immediately clear why I avoided using a mongo-models method and did something directly. So I've ended up adding "Here be dragons" comment blocks and documentation.

Proposal

Allow users to turn off validation of all mongo query results. After careful analysis of the current implementation, I think the only way this would work is to have an option that completely removes the result = new this(...) logic in this.resultFactory for all methods (or just don't pass results through this.resultFactory at all). The only time validation would happen is when the developer is instantiating their custom model that extends mongo-models (like the new Customer(...) example in the readme). I do realize that validating the mongo result is sometimes the main or only reason why methods are redefined - so if this is removed, they no longer have a purpose and their presence is a bit awkward.

Is this problem relatable? Thoughts? (@jedireza and anyone else)

*byId methods don't work with custom IDs

I always get Argument passed in must be a single String of 12 bytes or a string of 24 hex characters error when trying to use *byId methods of mongo-models with custom IDs (for example: D1, D4).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.