Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/vadimdemedes/mongorito
🍹 MongoDB ODM for Node.js apps based on Redux
https://github.com/vadimdemedes/mongorito
mongodb odm
Last synced: 3 months ago
JSON representation
🍹 MongoDB ODM for Node.js apps based on Redux
- Host: GitHub
- URL: https://github.com/vadimdemedes/mongorito
- Owner: vadimdemedes
- Archived: true
- Created: 2012-02-16T09:53:59.000Z (almost 13 years ago)
- Default Branch: master
- Last Pushed: 2020-03-24T17:23:14.000Z (almost 5 years ago)
- Last Synced: 2024-04-15T12:13:35.829Z (8 months ago)
- Topics: mongodb, odm
- Language: JavaScript
- Homepage:
- Size: 1.96 MB
- Stars: 1,389
- Watchers: 35
- Forks: 103
- Open Issues: 43
-
Metadata Files:
- Readme: readme.md
Awesome Lists containing this project
README
> Lightweight and flexible MongoDB ODM for Node.js apps based on Redux.
[![Build Status](https://travis-ci.org/vadimdemedes/mongorito.svg?branch=master)](https://travis-ci.org/vadimdemedes/mongorito) [![Coverage Status](https://coveralls.io/repos/vadimdemedes/mongorito/badge.svg?branch=master&service=github)](https://coveralls.io/github/vadimdemedes/mongorito?branch=master)
## Features
**Flexible**
Mongorito is based on [Redux](https://github.com/reactjs/redux), which opens the doors for customizing literally everything - from model's state (reducers) to the behavior of core methods, like `set()`, `save()` or `find()`.
Each model instance has a separate Redux store, which ensures isolation between other models and easy extensibility.
**No schemas**
If MongoDB doesn't enforce schemas, why would Mongorito do? Enjoy the schema-free data management with Mongorito the same way you do in `mongo` console.
**Lightweight**
Mongorito is betting on 3rd-party plugins to deliver extra functionality to developers. Mongorito ships with a barebones model with basic get/set, save/remove and querying functionality and let's you be in control of what's included and what's not.
Mongorito is basically a tiny Redux-based application, which uses the official MongoDB driver and [mquery](https://github.com/aheckmann/mquery) for querying. Not that amount of lines are relevant when measuring complexity, but each file (module) is less than 300 lines. Check out the source code and see for yourself!
## Quick overview
```js
const {Database, Model} = require('mongorito');const db = new Database('localhost/blog');
await db.connect();class Post extends Model {}
db.register(Post);
const post = new Post({
title: 'Steve Angello rocks',
author: {
name: 'Emma'
}
});await post.save();
post.set('author.name', 'Rick');
await post.save();await db.disconnect();
```*Note*: `await` won't work at top level, it's used to reduce the complexity of an example.
## Installation
```
$ npm install --save mongorito
```## Contents
- [Connection](#connection)
- [Models](#models)
- - [Creating a model](#creating-a-model)
- - [Working with fields](#working-with-fields)
- - [Saving or removing documents](#saving-or-removing-documents)
- - [Incrementing fields](#incrementing-fields)
- - [Embedding other models](#embedding-other-models)
- - [Configuration](#configuration)
- [Queries](#queries)
- [Plugins](#plugins)
- - [Using plugins](#using-plugins)
- - [Writing plugins](#writing-plugins)
- - [Extending model with new methods](#extending-model-with-new-methods)
- - [Modifying model's state](#modifying-models-state)
- - [Changing behavior using middleware](#changing-behavior-using-middleware)
- [Migrating from legacy version](#migrating-from-legacy-version)## Connection
Mongorito exports several own classes, as well as a few properties from the MongoDB driver:
```js
const {
Database,
Model,
Timestamp,
ObjectId,
MinKey,
MaxKey,
DBRef,
Long
} = require('mongorito');
````Database` and `Model` are Mongorito's own exports, all the other ones are exported straight from [`mongodb`](https://github.com/mongodb/node-mongodb-native) package for convenience. Normally, you'd need only `Database`, `Model` and `ObjectId`.
To connect, initialize a `Database`, which accepts a MongoDB connection string and an optional configuration which will be passed to mongodb's [connect function](http://mongodb.github.io/node-mongodb-native/2.2/api/MongoClient.html).
To connect and disconnect use `connect()`/`disconnect()` methods. Both returns a Promise.
For convenience, `await` will be used in all examples below, even though it doesn't work at top level.
```js
const {Database, Model} = require('mongorito');const db = new Database('localhost/blog', {
reconnectTries: 5
});await db.connect();
await db.disconnect();
```You don't have to wait until connection establishes to perform operations. Mongorito automatically executes pending operations once connection is up.
## Models
### Creating a model
Model is the connection between your data and a database. Each model represents a single collection. Model is a simple class, which doesn't even need to have any properties or methods.
```js
class Post extends Model {}
```For `Post` model to work and be aware of the database it's connected to, make sure to register it in the database we created earlier.
```js
db.register(Post);
```That's it, the `Post` model is good to go!
### Working with fields
To create a new document, create an instance of `Post` model.
```js
const post = new Post();
```Model's constructor also accepts an object of fields to instantiate the document with:
```js
const post = new Post({
title: 'Great post',
author: {
name: 'Sarah'
}
});
```Note, documents can contain nested fields and even models, just like in MongoDB.
To get one or all fields from the `post` document, use a `get()` method.
```js
const title = post.get('title');
//=> "Great post"const author = post.get('author.name');
//=> "Sarah"const data = post.get();
//=>
// {
// title: "Great post"
// author: {
// name: "Sarah"
// }
// }
```Similarly, use `set()` to update fields:
```js
// update fields one by one
post.set('title', 'Amazing post');
post.set('author.name', 'Monica');// or all at once
post.set({
title: 'Amazing post',
author: {
name: 'Monica'
}
});
```To remove a field, use `unset()`:
```js
// unset single fields
post.unset('title');
post.unset('author.name');// or multiple fields at once
post.unset(['title', 'author.name']);
```### Saving or removing documents
To create or update documents, simply call `save()`. Even though Mongorito differentiates these two operations internally, you don't have to care about that! Mongorito also infers the collection name from the model, so the instances of the model `Post` will be saved to `posts` collection.
```js
await post.save();
```When a document is saved, an `_id` field is automatically added.
```js
post.get('_id');
//=> ObjectId("5905cb6b543c3a50e03e810d")
```To remove a document, use `remove()`.
```js
await post.remove();
```To remove multiple documents, use `remove()` on the model itself with a query as an argument.
```js
await Post.remove({good: false});
```### Incrementing fields
Mongorito also provides a handy `increment()` method to increment or decrement numerical fields:
```js
const post = new Post({
views: 0
});await post.increment('views');
post.get('views');
//=> 1
```You can also supply a value to increment a field by a specific amount.
```js
await post.increment('views', 2);post.get('views');
//=> 3
```Multiple fields can be incremented at once, too.
```js
const post = new Post({
views: 10,
comments: 10
});await post.increment({
views: 2,
comments: 5
});post.get('views');
//=> 12post.get('comments');
//=> 15
```### Embedding other models
Just like MongoDB, Mongorito allows to effortlessly embed other models. They're transparently converted between JSON and Mongorito models.
To embed models, use `embeds()` method on the model itself to help Mongorito with the model serialization when saving/reading from the database. `embeds()` method accepts a field name, where the embedded document (or array of documents) resides.
Here's the quick overview on how it works. Note, that model registering via `register()` is skipped in the following example.
```js
class Post extends Model {}
class Author extends Model {}
class Comment extends Model {}Post.embeds('author', Author);
Post.embeds('comments', Comment);const post = new Post({
title: 'Great post',
author: new Author({name: 'Steve'}),
comments: [new Comment({body: 'Interesting!'})]
});await post.save();
```The above post will be saved to the database as:
```json
{
"title": "Great post",
"author": {
"name": "Steve"
},
"comments": [
{
"body": "Interesting!"
}
]
}
```You can also just pass objects instead of model instances and Mongorito will take care of that too.
```js
const post = new Post({
title: 'Great post',
author: {
name: 'Steve'
},
comments: [{
body: 'Interesting!'
}]
});
```When that document will be retrieved from the database next time, all embedded documents will be wrapped with their corresponding models.
```js
const post = await Post.findOne();const author = post.get('author');
//=> Author { name: "Steve" }author.get('name');
//=> "Steve"
```### Configuration
#### Using a different collection name
In case you need to store documents in a custom collection, you can override the default one using `collection()` method.
```js
class Post extends Model {
collection() {
return 'awesome_posts';
}
}
```## Queries
Mongorito uses [mquery](https://github.com/aheckmann/mquery) to provide a simple and comfortable API for querying. It inherits all the methods from `mquery` with a few exceptions, which will be documented below. For documentation, please check out mquery's API - https://github.com/aheckmann/mquery.
Here's a quick overview of how querying works in Mongorito. All documents returned from queries are automatically wrapped into their models.
```js
// find all posts
await Post.find();// find all amazing posts
await Post.find({amazing: true});
await Post.where('amazing', true).find();// find 5 recent posts
await Post
.limit(5)
.sort('created_at', 'desc')
.find();// find one post
await Post.findOne({incredible: 'yes'});// count posts
await Post.count({super: false});
```## Plugins
### Using plugins
To use a 3rd-party plugin, all you have to do is to call `use()` method.
```js
const timestamps = require('mongorito-timestamps');db.use(timestamps());
```This will apply [mongorito-timestamps](https://github.com/vadimdemedes/mongorito-timestamps) to models registered after that.
If you want to apply the plugin to a specific model only, call it on the model itself.
```js
Post.use(timestamps());
```### Writing plugins
A plugin is simply a function that accepts a model. A familiarity with Redux and its concepts will help you tremendously with writing plugins.
```js
const myPlugin = model => {
// do anything with model (Post, in this case)
};Post.use(myPlugin);
```Feel free to assign new methods to the model or instances, add new middleware, modify the model's state and anything that comes to your mind.
### Extending model with new methods
Here's an example of adding a class method and an instance method to a `Post` model.
```js
const extendPost = Post => {
Post.findRecent = function () {
return this
.limit(5)
.sort('created_at', 'desc')
.find();
};Post.prototype.makeAmazing = function () {
this.set('amazing', true);
};
};Post.use(extendPost);
const post = new Post();
post.makeAmazing();
post.get('amazing');
//=> trueconst posts = await Post.findRecent();
//=> [Post, Post, Post]
```### Modifying model's state
If you plugin needs to have its own state, you can modify the model's reducer using `modifyReducer()` method. It accepts a function, which receives the existing reducer shape as an argument and should return a new object with added reducers.
```js
const customReducer = (state = null, action) => {
// reducer code...
};const extendReducer = model => {
model.modifyReducer(reducer => {
return {
...reducer,
customState: customReducer
}
});
};
```### Changing behavior using middleware
Middleware can be used to change or modify the behavior of model's operations. You can interact with everything, from get/set operations to queries.
To add plugin's custom middleware to the default middleware stack, return it from the plugin function.
```js
const myPlugin = () => {
return store => next => action => {
// middleware code...
};
};
```Obviously, to detect what kind of action is being handled, you need to be aware of Mongorito's action types.
```js
const {ActionTypes} = require('mongorito');const myPlugin = () => {
return store => next => action => {
if (action.type === ActionTypes.SET) {
// alter set() behavior
}return next(action);
};
};
```Again, the middleware is identical to the middleware you're used to when writing apps with Redux. There are only 2 new properties added to the `store`:
- `model` - instance of the model (document) the middleware is currently running in. If middleware is running at the model level (without instantiated model), it will be `undefined`.
- `modelClass` - model class (`Post`, for example).Here's an example on how to access all props of the store:
```js
const myPlugin = () => {
return ({getState, dispatch, model, modelClass}) => next => action => {
// `getState()` and `dispatch()` are from Redux itself
// `model` is `post`
// `modelClass` is `Post`return next(action);
};
};Post.use(myPlugin);
const post = new Post();
await post.save();
```For examples on how to write middleware, check out Mongorito's native ones - https://github.com/vadimdemedes/mongorito/tree/master/lib/middleware.
## Migrating from legacy version
### Connection
Before:
```js
const mongorito = require('mongorito');mongorito.connect('localhost/blog');
```After:
```js
const {Database} = require('mongorito');const db = new Database('localhost/blog');
await db.connect();
```## License
MIT © [Vadim Demedes](https://github.com/vadimdemedes)