
Product
Announcing Socket Fix 2.0
Socket Fix 2.0 brings targeted CVE remediation, smarter upgrade planning, and broader ecosystem support to help developers get to zero alerts.
Mongore
provides an easy way to create native JavaScript classes with MongoDB backend.
The idea is to make Mongo pretty much invisible and still enjoy Mongo-powered persistency and fast search.
Fair warning: This project is still undertested, use at your own risk.
Lets take a look at a quick example. First, lets connect to MongoDB (for simplicity, I will ignore callbacks and validations for now):
const MongoClient = require('mongodb').MongoClient
const Mongore = require('mongore')
Mongore.Client.connect(MongoClient, "mongodb://localhost:27017/dogsDb", "dogsDb");
Now lets define a model:
// define a class to represent a dog
class Dog extends Mongore.Model
{
// create the dog with name and breed.
constructor(name, breed)
{
super();
this.name = name;
this.breed = breed;
}
// bark.
bark()
{
if (this.breed == "chihuahua") {
console.log("*angry rat noises*");
}
else {
console.log("Woof!");
}
}
// give dog a bone.
// note: cooked bones are actually dangerous for dogs, you should never give your dog cooked bones!
giveBone()
{
this.bonesEaten++;
this.bark();
}
// calculate dog's age based on when it was created in DB.
get age()
{
return (new Date()).getYear() - this.mongore.creationTime.getYear());
}
}
// define the dog fields that will be stored in DB:
Dog.buildModel(
fields: {
_id: new Mongore.Fields.StringField({source: "name", maxLength: 16}),
breed: new Mongore.Fields.ChoiceField({choices: ["chihuahua", "bulldog", "golden", "lab"]}),
bonesEaten: new Mongore.Fields.NumericField({default: 0, parser: parseInt})
}
);
Using the model is super easy!
var myDog = new Dog("Rex", "golden");
myDog.giveBone();
myDog.mongore.save();
Or to load a dog from db..
Dog.mongore.load("Rex", (myDog) => {
console.log("My dog ate ", myDog.bonesEaten, "bones.");
});
Mongore's key features are:
To install via npm:
npm install mongore
Before using Mongore, you need to initialize a connection (note: its OK to define your models before initializing, you only need to connect before loading / saving objects):
const Mongore = require('./mongore');
const MongoClient = require('mongodb').MongoClient;
Mongore.Client.connect(MongoClient, Config.db.url, Config.db.name, () => {
// ready for action!
})
As you can see Mongore
don't require MongoDB
directly. This means you can use whatever version you want or wrap Mongo's client yourself, as long as the basic API of insertOne, updateOne, findOne is kept.
Now lets define some Models, ie. persistent JS objects with MongoDB backend. For this example, we'll make a simple forum app.
We'll start with the User
model:
/**
* Represent a user.
*/
class User extends Mongore.Model
{
/**
* Create a new user.
*/
constructor(username, password, userType)
{
super();
this.username = username;
this.password = password;
this.userType = userType;
}
/**
* Get if admin user.
*/
get isAdmin()
{
return this.userType === "admin";
}
/**
* Create and return a new board (don't save it).
*/
createBoard(name)
{
if (!this.isAdmin) {
throw new Error("Only admins can open boards!");
}
return new Board(name, this);
}
}
User.buildModel(
fields: {
_id: new Mongore.Fields.StringField({source: "username", maxLength: 16}),
userType: new Mongore.Fields.ChoiceField({choices: ["guest", "registered", "admin"]}),
password: new Mongore.Fields.StringField()
}
);
So what's going on above?
User
class that get username, password, and type. It also have a method to create a board (will be defined next) and a getter to check if type is admin.string
with max length of 16 characters, and we make it a proxy of the username
property.Once the fields are defined, you can use them just as you would with any JS property from the instance, as demonstrated in the constructor and in the class's methods.
As you probably know, MongoDB works with collections
, and while you can let MongoDB create collections with default values automatically, its best to define them with validations and other metadata (like indexes). So lets ask Mongore
to create a collection for Users:
User.mongore.createCollection(() => {
console.log("Users collection is now ready!");
});
Obviously you don't have to call createCollection
every time your app starts (although there's no harm in that), so its best to create short init-db.js
script, and put all the createCollection()
calls there. Call this script whenever you change or define a new Model.
Now lets create and save a new user:
var user = new User("Billy", "pass1234", "admin");
user.mongore.save(() => {
console.log("Great success!");
},
(err) => {
console.log("Error! ", err);
});
That's it! You can now check your MongoDB and see a Users
collection with a single object, Billy
.
So how do we load the user we just created? Easy!
User.mongore.load("Billy", (user) => {
console.log(`Welcome back, ${user.username}!`);
});
mongore
PropertyAll the fields you define in your model are created as-if they were plain properties under your class. However, as you noticed above, we also have the mongore
object under the instance and the class itself.
This object contains the API and metadata of mongore
, and represent the bridge between your plain JS object and the Database.
load()
and createCollection()
methods.save()
method and other useful functions described later.Continuing with our example, time to define the Board
model to represent a board:
/**
* Represent a board.
*/
class Board extends Mongore.Model
{
/**
* Create a new board.
*/
constructor(name, creator)
{
super();
this.name = name;
this.creator = creator;
}
/**
* Create and return a new message (don't save it).
*/
createMessage(user, text)
{
return new Message(this, user, text);
}
}
Board.buildModel(
fields: {
_id: new Mongore.Fields.StringField({source: "name", maxLength: 16}),
creator: new Mongore.Fields.ForeignKeyField({model: User, canBeNull: false}),
}
);
The name
field is similar to User.username
and will not be discussed here. The new field, creator
is interesting.
A ForeignKeyField
is a field that expect an instance of the provided Model (in this case, User
). In Mongo, it will be stores as the instance's _id
field. When an instance of Board
is loaded, it will also load all its foreign keys.
Note that the success callback will be called before foreign keys are loaded. Lets take a look at how we load a Board
instance, wait for the User
foreign key to be ready, then performing an action:
Board.mongore.load("myBoard", (board) => {
board.creator.mongore.onLoaded((user) => {
board.createMessage(user, "Hi all, just logged in! - XOXO").mongore.save();
})
})
And finally, lets create the message Model:
/**
* Represent a single text message in one of the forum's boards.
*/
class Message extends Mongore.Model
{
/**
* Create a new message.
*/
constructor(board, author, msg)
{
super();
this.board = board;
this.author = author;
this.msg = msg;
}
/**
* Give this message a thumb up.
*/
upvote()
{
this.score++;
}
/**
* Give this message a thumb down.
*/
downvote()
{
this.score--;
}
}
Message.buildModel(
fields: {
board: new Mongore.Fields.ForeignKeyField({model: Board, canBeNull: false}),
author: new Mongore.Fields.ForeignKeyField({model: User, canBeNull: false}),
msg: new Mongore.Fields.StringField(),
score: new Mongore.Fields.NumericField({parser: parseInt, index: true})
}
);
The above is pretty similar to the previous models, with a just a minor thing to notice: index: true
will set the score
field as an index, which means we can quickly search on it.
That's it for the example, now lets explore the API!
The main function that turn your JS class into a proper Model is the buildModel()
method, as demonstrated in the example above.
This method accept the following arguments (as a dictionary):
When you build your Model you specify one or more fields to include in the DB scheme. As briefly mentioned before, every field you define will create a getter and setter in your class with the same name. In addition, every field define validations and cleanup logic to trigger when writing / reading from DB, as well as validators to set in MongoDB itself (there are validations in app level and in Mongo's level).
There are several built-in Field types (its really easy to define new ones) and every field accept different arguments. However, all fields support the following:
Now lets describe all the field types:
A generic field type without validations or cleanup logic.
A boolean field.
A choice field that enforce the value to be from a set of options.
Have the following additional options:
A date field (store JavaScript Date objects).
Have the following additional options:
A dictionary field.
Have the following additional options:
Note: when a schema is defined, you also have to define a default value that matches it.
A field that point on another Model's instance, and load it automatically when loaded from DB (in MongoDB its stored as _id).
Have the following additional options:
A list field.
Have the following additional options:
A number (int or float) field.
Have the following additional options:
A string field.
Have the following additional options:
The Mongore.Model
is the base class all your models should extend. Except for the fields you define, which will became getters and setters under your class, most of your model's API will remain unchanged as mongore's API is kept under the mongore
accessor.
However, there are some callbacks you can implement to respond to DB related events:
Will be called whenever the instance is successfully loaded from DB.
Will be called before saving an instance to the DB.
Will be called after an instance was successfully saved.
Will be called after the instance was deleted from DB.
Every Model class you define will be coupled with a mongore
API that implements the Model-related API.
This is the main object you access to use Mongore when you don't have an instance.
The following is the key functionality of the class-level API:
Load and return an instance from DB.
Delete an instance from DB.
Note: won't invoke afterDeletedFromDB
callbacks.
Delete multiple instances from DB.
Note: won't invoke afterDeletedFromDB
callbacks.
Create the collection (with all validation and indexes) for this Model.
Drop the collection of this Model.
Get the default value for a given field name.
Get the collection name used for this model.
List with all field names of this model (not including internal stuff).
Every model instance you create will be coupled with a mongore
API that implements the Instance API.
This is the main object you access to use Mongore.
The following is the key functionality of the instance-level API:
Save the instance to DB.
Reload this object from DB.
Delete this object from DB.
Optional callback to invoke on first time this instance is loaded from DB.
Return if this instance have any changed fields that needs to be saved to DB.
Get a list of field names that are dirty.
Set a field to its default value.
Get the creation time of the object (first time it was insert to DB). Note: does not update automatically, call reload() to update this.
Get the last update() / insert() time of this object. Note: does not update automatically, call reload() to update this.
An auto-incremented number representing the number of times this object was updated in DB. Note: does not update automatically, call reload() to update this.
Get the instance primary id field.
Return if the object was loaded from DB.
Return if this specific instance was ever saved to DB.
Return if the specific instance was either loaded from DB, or ever saved to it. In other words, if its present in DB.
Return last time this object was loaded (or reloaded) from DB.
Collection name used for this instance.
List with all field names of this model (not including internal stuff).
If you want to receive logs from Mongore
, you can set a logger instance with Mongore.setLogger()
.
The logger should implement all the basic logging methods (error, warn, info, verbose, debug, silly), and it is recommended to add an automatic label to mark that these logs originalte from Mongore, so they won't get mixed up with your other logs (or as an alternative - send to a different file).
Mongore
is distributed under the MIT license and is free for any purpose.
FAQs
Creating native JavaScript classes with MongoDB backend.
We found that mongore demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Product
Socket Fix 2.0 brings targeted CVE remediation, smarter upgrade planning, and broader ecosystem support to help developers get to zero alerts.
Security News
Socket CEO Feross Aboukhadijeh joins Risky Business Weekly to unpack recent npm phishing attacks, their limited impact, and the risks if attackers get smarter.
Product
Socket’s new Tier 1 Reachability filters out up to 80% of irrelevant CVEs, so security teams can focus on the vulnerabilities that matter.