UNPKG

13.2 kBMarkdownView Raw
1<h1 align="center">
2 <br>
3 <img width="400" src="media/logo.png">
4 <br>
5 <br>
6</h1>
7
8> Lightweight and flexible MongoDB ODM for Node.js apps based on Redux.
9
10[![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)
11
12
13## Features
14
15**Flexible**
16
17Mongorito 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()`.
18
19Each model instance has a separate Redux store, which ensures isolation between other models and easy extensibility.
20
21**No schemas**
22
23If 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.
24
25**Lightweight**
26
27Mongorito 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.
28
29Mongorito 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!
30
31
32## Quick overview
33
34```js
35const {Database, Model} = require('mongorito');
36
37const db = new Database('localhost/blog');
38await db.connect();
39
40class Post extends Model {}
41
42db.register(Post);
43
44const post = new Post({
45 title: 'Steve Angello rocks',
46 author: {
47 name: 'Emma'
48 }
49});
50
51await post.save();
52
53post.set('author.name', 'Rick');
54await post.save();
55```
56
57*Note*: `await` won't work at top level, it's used to reduce the complexity of an example.
58
59
60## Installation
61
62```
63$ npm install --save mongorito
64```
65
66
67## Contents
68
69- [Connection](#connection)
70- [Models](#models)
71- - [Creating a model](#creating-a-model)
72- - [Working with fields](#working-with-fields)
73- - [Saving or removing documents](#saving-or-removing-documents)
74- - [Incrementing fields](#incrementing-fields)
75- - [Embedding other models](#embedding-other-models)
76- - [Configuration](#configuration)
77- [Queries](#queries)
78- [Plugins](#plugins)
79- - [Using plugins](#using-plugins)
80- - [Writing plugins](#writing-plugins)
81- - [Extending model with new methods](#extending-model-with-new-methods)
82- - [Modifying model's state](#modifying-models-state)
83- - [Changing behavior using middleware](#changing-behavior-using-middleware)
84- [Migrating from legacy version](#migrating-from-legacy-version)
85
86
87## Connection
88
89Mongorito exports several own classes, as well as a few properties from the MongoDB driver:
90
91```js
92const {
93 Database,
94 Model,
95 Timestamp,
96 ObjectId,
97 MinKey,
98 MaxKey,
99 DBRef,
100 Long
101} = require('mongorito');
102```
103
104`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`.
105
106To connect, initialize a `Database`, which accepts a MongoDB connection string and use `connect()` method, which returns a Promise.
107
108For convenience, `await` will be used in all examples below, even though it doesn't work at top level.
109
110```js
111const {Database, Model} = require('mongorito');
112
113const db = new Database('localhost/blog');
114await db.connect();
115```
116
117You don't have to wait until connection establishes to perform operations. Mongorito automatically executes pending operations once connection is up.
118
119## Models
120
121### Creating a model
122
123Model 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.
124
125```js
126class Post extends Model {}
127```
128
129For `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.
130
131```js
132db.register(Post);
133```
134
135That's it, the `Post` model is good to go!
136
137### Working with fields
138
139To create a new document, create an instance of `Post` model.
140
141```js
142const post = new Post();
143```
144
145Model's constructor also accepts an object of fields to instantiate the document with:
146
147```js
148const post = new Post({
149 title: 'Great post',
150 author: {
151 name: 'Sarah'
152 }
153});
154```
155
156Note, documents can contain nested fields and even models, just like in MongoDB.
157
158To get one or all fields from the `post` document, use a `get()` method.
159
160```js
161const title = post.get('title');
162//=> "Great post"
163
164const author = post.get('author.name');
165//=> "Sarah"
166
167const data = post.get();
168//=>
169// {
170// title: "Great post"
171// author: {
172// name: "Sarah"
173// }
174// }
175```
176
177Similarly, use `set()` to update fields:
178
179```js
180// update fields one by one
181post.set('title', 'Amazing post');
182post.set('author.name', 'Monica');
183
184// or all at once
185post.set({
186 title: 'Amazing post',
187 author: {
188 name: 'Monica'
189 }
190});
191```
192
193To remove a field, use `unset()`:
194
195```js
196// unset single fields
197post.unset('title');
198post.unset('author.name');
199
200// or multiple fields at once
201post.unset(['title', 'author.name']);
202```
203
204### Saving or removing documents
205
206To 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.
207
208```js
209await post.save();
210```
211
212When a document is saved, an `_id` field is automatically added.
213
214```js
215post.get('_id');
216//=> ObjectId("5905cb6b543c3a50e03e810d")
217```
218
219To remove a document, use `remove()`.
220
221```js
222await post.remove();
223```
224
225To remove multiple documents, use `remove()` on the model itself with a query as an argument.
226
227```js
228await Post.remove({good: false});
229```
230
231### Incrementing fields
232
233Mongorito also provides a handy `increment()` method to increment or decrement numerical fields:
234
235```js
236const post = new Post({
237 views: 0
238});
239
240await post.increment('views');
241
242post.get('views');
243//=> 1
244```
245
246You can also supply a value to increment a field by a specific amount.
247
248```js
249await post.increment('views', 2);
250
251post.get('views');
252//=> 3
253```
254
255Multiple fields can be incremented at once, too.
256
257```js
258const post = new Post({
259 views: 10,
260 comments: 10
261});
262
263await post.increment({
264 views: 2,
265 comments: 5
266});
267
268post.get('views');
269//=> 12
270
271post.get('comments');
272//=> 15
273```
274
275### Embedding other models
276
277Just like MongoDB, Mongorito allows to effortlessly embed other models. They're transparently converted between JSON and Mongorito models.
278
279To 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.
280
281Here's the quick overview on how it works. Note, that model registering via `register()` is skipped in the following example.
282
283```js
284class Post extends Model {}
285class Author extends Model {}
286class Comment extends Model {}
287
288Post.embeds('author', Author);
289Post.embeds('comments', Comment);
290
291const post = new Post({
292 title: 'Great post',
293 author: new Author({name: 'Steve'}),
294 comments: [new Comment({body: 'Interesting!'})]
295});
296
297await post.save();
298```
299
300The above post will be saved to the database as:
301
302```json
303{
304 "title": "Great post",
305 "author": {
306 "name": "Steve"
307 },
308 "comments": [
309 {
310 "body": "Interesting!"
311 }
312 ]
313}
314```
315
316You can also just pass objects instead of model instances and Mongorito will take care of that too.
317
318```js
319const post = new Post({
320 title: 'Great post',
321 author: {
322 name: 'Steve'
323 },
324 comments: [{
325 body: 'Interesting!'
326 }]
327});
328```
329
330When that document will be retrieved from the database next time, all embedded documents will be wrapped with their corresponding models.
331
332```js
333const post = await Post.findOne();
334
335const author = post.get('author');
336//=> Author { name: "Steve" }
337
338author.get('name');
339//=> "Steve"
340```
341
342### Configuration
343
344#### Using a different collection name
345
346In case you need to store documents in a custom collection, you can override the default one using `collection()` method.
347
348```js
349class Post extends Model {
350 collection() {
351 return 'awesome_posts';
352 }
353}
354```
355
356## Queries
357
358Mongorito 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.
359
360Here's a quick overview of how querying works in Mongorito. All documents returned from queries are automatically wrapped into their models.
361
362```js
363// find all posts
364await Post.find();
365
366// find all amazing posts
367await Post.find({amazing: true});
368await Post.where('amazing', true).find();
369
370// find 5 recent posts
371await Post
372 .limit(5)
373 .sort('created_at', 'desc')
374 .find();
375
376// find one post
377await Post.findOne({incredible: 'yes'});
378
379// count posts
380await Post.count({super: false});
381```
382
383## Plugins
384
385### Using plugins
386
387To use a 3rd-party plugin, all you have to do is to call `use()` method.
388
389```js
390const timestamps = require('mongorito-timestamps');
391
392db.use(timestamps);
393```
394
395This will apply [mongorito-timestamps](https://github.com/vadimdemedes/mongorito-timestamps) to models registered after that.
396
397If you want to apply the plugin to a specific model only, call it on the model itself.
398
399```js
400Post.use(timestamps);
401```
402
403### Writing plugins
404
405A plugin is simply a function that accepts a model. A familiarity with Redux and its concepts will help you tremendously with writing plugins.
406
407```js
408const myPlugin = model => {
409 // do anything with model (Post, in this case)
410};
411
412Post.use(myPlugin);
413```
414
415Feel 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.
416
417### Extending model with new methods
418
419Here's an example of adding a class method and an instance method to a `Post` model.
420
421```js
422const extendPost = Post => {
423 Post.findRecent = function () {
424 return this
425 .limit(5)
426 .sort('created_at', 'desc')
427 .find();
428 };
429
430 Post.prototype.makeAmazing = function () {
431 this.set('amazing', true);
432 };
433};
434
435Post.use(extendPost);
436
437const post = new Post();
438post.makeAmazing();
439post.get('amazing');
440//=> true
441
442const posts = await Post.findRecent();
443//=> [Post, Post, Post]
444```
445
446### Modifying model's state
447
448If 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.
449
450```js
451const customReducer = (state = null, action) => {
452 // reducer code...
453};
454
455const extendReducer = model => {
456 model.modifyReducer(reducer => {
457 return {
458 ...reducer,
459 customState: customReducer
460 }
461 });
462};
463```
464
465### Changing behavior using middleware
466
467Middleware can be used to change or modify the behavior of model's operations. You can interact with everything, from get/set operations to queries.
468
469To add plugin's custom middleware to the default middleware stack, return it from the plugin function.
470
471```js
472const myPlugin = () => {
473 return store => next => action => {
474 // middleware code...
475 };
476};
477```
478
479Obviously, to detect what kind of action is being handled, you need to be aware of Mongorito's action types.
480
481```js
482const {ActionTypes} = require('mongorito');
483
484const myPlugin = () => {
485 return store => next => action => {
486 if (action.type === ActionTypes.SET) {
487 // alter set() behavior
488 }
489
490 return next(action);
491 };
492};
493```
494
495Again, 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`:
496
497- `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`.
498- `modelClass` - model class (`Post`, for example).
499
500Here's an example on how to access all props of the store:
501
502```js
503const myPlugin = () => {
504 return ({getState, dispatch, model, modelClass}) => next => action => {
505 // `getState()` and `dispatch()` are from Redux itself
506 // `model` is `post`
507 // `modelClass` is `Post`
508
509 return next(action);
510 };
511};
512
513Post.use(myPlugin);
514
515const post = new Post();
516await post.save();
517```
518
519For examples on how to write middleware, check out Mongorito's native ones - https://github.com/vadimdemedes/mongorito/tree/master/lib/middleware.
520
521
522## Migrating from legacy version
523
524### Connection
525
526Before:
527
528```js
529const mongorito = require('mongorito');
530
531mongorito.connect('localhost/blog');
532```
533
534After:
535
536```js
537const {Database} = require('mongorito');
538
539const db = new Database('localhost/blog');
540await db.connect();
541```
542
543
544## License
545
546MIT © [Vadim Demedes](https://github.com/vadimdemedes)