Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to Feathers Buzzard (v3) #224

Merged
merged 2 commits into from
Dec 3, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ language: node_js
services: mongodb
node_js:
- 'node'
- '8'
- '6'
494 changes: 247 additions & 247 deletions CHANGELOG.md

Large diffs are not rendered by default.

261 changes: 199 additions & 62 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,110 +1,247 @@
feathers-mongoose
================
# feathers-mongoose

[![Greenkeeper badge](https://badges.greenkeeper.io/feathersjs/feathers-mongoose.svg)](https://greenkeeper.io/)
[![Greenkeeper badge](https://badges.greenkeeper.io/feathersjs-ecosystem/feathers-mongoose.svg)](https://greenkeeper.io/)

[![Build Status](https://travis-ci.org/feathersjs/feathers-mongoose.png?branch=master)](https://travis-ci.org/feathersjs/feathers-mongoose)
[![Code Climate](https://codeclimate.com/github/feathersjs/feathers-mongoose/badges/gpa.svg)](https://codeclimate.com/github/feathersjs/feathers-mongoose)
[![Test Coverage](https://codeclimate.com/github/feathersjs/feathers-mongoose/badges/coverage.svg)](https://codeclimate.com/github/feathersjs/feathers-mongoose/coverage)
[![Dependency Status](https://img.shields.io/david/feathersjs/feathers-mongoose.svg?style=flat-square)](https://david-dm.org/feathersjs/feathers-mongoose)
[![Build Status](https://travis-ci.org/feathersjs-ecosystem/feathers-mongoose.png?branch=master)](https://travis-ci.org/feathersjs-ecosystem/feathers-mongoose)
[![Dependency Status](https://img.shields.io/david/feathersjs-ecosystem/feathers-mongoose.svg?style=flat-square)](https://david-dm.org/feathersjs-ecosystem/feathers-mongoose)
[![Download Status](https://img.shields.io/npm/dm/feathers-mongoose.svg?style=flat-square)](https://www.npmjs.com/package/feathers-mongoose)
[![Slack Status](http://slack.feathersjs.com/badge.svg)](http://slack.feathersjs.com)


> Create a [Mongoose](http://mongoosejs.com/) ORM wrapped service for [FeathersJS](https://github.com/feathersjs).


## Installation
A [Feathers](https://feathersjs.com) database adapter for [Mongoose](http://mongoosejs.com/), an object modeling tool for [MongoDB](https://www.mongodb.org/).

```bash
npm install feathers-mongoose --save
$ npm install --save mongoose feathers-mongoose
```

## Documentation
> __Important:__ `feathers-mongoose` implements the [Feathers Common database adapter API](https://docs.feathersjs.com/api/databases/common.html) and [querying syntax](https://docs.feathersjs.com/api/databases/querying.html).

Please refer to the [Feathers database adapter documentation](https://docs.feathersjs.com/api/databases/common.html) for more details or directly at:
> This adapter also requires a [running MongoDB](https://docs.mongodb.com/getting-started/shell/#) database server.

- [Mongoose](https://docs.feathersjs.com/api/databases/mongoose.html) - The detailed documentation for this adapter
- [Extending](https://docs.feathersjs.com/api/databases/common.html#extending-adapters) - How to extend a database adapter
- [Pagination](https://docs.feathersjs.com/api/databases/common.html#pagination) - How to use pagination
- [Querying and Sorting](https://docs.feathersjs.com/api/databases/querying.html) - The common adapter querying mechanism and sorting for the database adapter

## Getting Started
## API

Creating an Mongoose service is this simple (make sure your MongoDB server is up and running):
### `service(options)`

Returns a new service instance initialized with the given options. `Model` has to be a Mongoose model. See the [Mongoose Guide](http://mongoosejs.com/docs/guide.html) for more information on defining your model.

```js
var mongoose = require('mongoose');
var MongooseModel = require('./models/mymodel')
var mongooseService = require('feathers-mongoose');
const mongoose = require('mongoose');
const service = require('feathers-mongoose');

// A module that exports your Mongoose model
const Model = require('./models/message');

// Make Mongoose use the ES6 promise
mongoose.Promise = global.Promise;

// Connect to a local database called `feathers`
mongoose.connect('mongodb://localhost:27017/feathers');

app.use('/todos', mongooseService({
Model: MongooseModel
}));
app.use('/messages', service({ Model }));
app.use('/messages', service({ Model, lean, id, events, paginate }));
```

__Options:__

- `Model` (**required**) - The Mongoose model definition
- `lean` (*optional*, default: `true`) - Runs queries faster by returning plain objects instead of Mongoose models.
- `id` (*optional*, default: `'_id'`) - The name of the id field property.
- `events` (*optional*) - A list of [custom service events](https://docs.feathersjs.com/api/events.html#custom-events) sent by this service
- `paginate` (*optional*) - A [pagination object](https://docs.feathersjs.com/api/databases/common.html#pagination) containing a `default` and `max` page size
- `discriminators` (*optional*) - A list of mongoose models that inherit from `Model`.

> **Important:** To avoid odd error handling behaviour, always set `mongoose.Promise = global.Promise`. If not available already, Feathers comes with a polyfill for native Promises.

<!-- -->

> **Important:** When setting `lean` to `false`, Mongoose models will be returned which can not be modified unless they are converted to a regular JavaScript object via `toObject`.

<!-- -->

> **Note:** You can get access to the Mongoose model via `this.Model` inside a [hook](https://docs.feathersjs.com/api/hooks.html) and use it as usual. See the [Mongoose Guide](http://mongoosejs.com/docs/guide.html) for more information on defining your model.

### params.mongoose

When making a [service method](https://docs.feathersjs.com/api/services.html) call, `params` can contain a `mongoose` property which allows you to modify the options used to run the Mongoose query. Normally, this will be set in a before [hook](https://docs.feathersjs.com/api/hooks.html):

```js
app.service('messages').hooks({
before: {
patch(context) {
// Set some additional Mongoose options
// The adapter tries to use sane defaults
// but they can always be changed here
context.params.mongoose = {
runValidators: true,
setDefaultsOnInsert: true
}
}
}
});
```

See the [Mongoose Guide](http://mongoosejs.com/docs/guide.html) for more information on defining your model.
The `mongoose` property is also useful for performing upserts on a `patch` request. "Upserts" do an update if a matching record is found, or insert a record if there's no existing match. The following example will create a document that matches the `data`, or if there's already a record that matches the `params.query`, that record will be updated.

```js
const data = { address: '123', identifier: 'my-identifier' }
const params = {
query: { address: '123' },
mongoose: { upsert: true }
}
app.service('address-meta').patch(null, data, params)
```

### Complete Example

Here's a complete example of a Feathers server with a `message` mongoose-service.
## Example

Here's a complete example of a Feathers server with a `messages` Mongoose service.

```
$ npm install @feathersjs/feathers @feathersjs/errors @feathersjs/express mongoose feathers-mongoose
```

In `message-model.js`:

```js
const feathers = require('feathers');
const rest = require('feathers-rest');
const socketio = require('feathers-socketio');
const errors = require('feathers-errors');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const service = require('feathers-mongoose');

// Require your models
const Message = require('./models/message');
const Schema = mongoose.Schema;
const MessageSchema = new Schema({
text: {
type: String,
required: true
}
});
const Model = mongoose.model('Message', MessageSchema);

// Tell mongoose to use native promises
// See http://mongoosejs.com/docs/promises.html
mongoose.Promise = global.Promise;
module.exports = Model;
```

// Connect to your MongoDB instance(s)
mongoose.connect('mongodb://localhost:27017/feathers');
Then in `app.js`:

```js
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const socketio = require('@feathersjs/socketio');

// Create a feathers instance.
const app = feathers()
// Enable Socket.io
.configure(socketio())
// Enable REST services
.configure(rest())
// Turn on JSON parser for REST services
.use(bodyParser.json())
// Turn on URL-encoded parser for REST services
.use(bodyParser.urlencoded({extended: true}));
const mongoose = require('mongoose');
const service = require('feathers-mongoose');

const Model = require('./message-model');

mongoose.Promise = global.Promise;

// Connect to your MongoDB instance(s)
mongoose.connect('mongodb://localhost:27017/feathers', {
useMongoClient: true
});

// Create an Express compatible Feathers application instance.
const app = express(feathers());

// Turn on JSON parser for REST services
app.use(express.json());
// Turn on URL-encoded parser for REST services
app.use(express.urlencoded({extended: true}));
// Enable REST services
app.configure(express.rest());
// Enable Socket.io services
app.configure(socketio());
// Connect to the db, create and register a Feathers service.
app.use('messages', service({,
Model: Message,
app.use('/messages', service({
Model,
lean: true, // set to false if you want Mongoose documents returned
paginate: {
default: 2,
max: 4
}
}));
app.use(express.errorHandler());

// Create a dummy Message
app.service('messages').create({
text: 'Message created on server'
}).then(function(message) {
console.log('Created message', message);
});

// Start the server.
const port = 3030;
app.listen(port, () => {
console.log(`Feathers server listening on port ${port}`);
});
```

You can run this example by using `node app` and go to [localhost:3030/messages](http://localhost:3030/messages).

## Querying, Validation

Mongoose by default gives you the ability to add [validations at the model level](http://mongoosejs.com/docs/validation.html). Using an error handler like the one that [comes with Feathers](https://github.com/feathersjs/feathers-errors/blob/master/src/error-handler.js) your validation errors will be formatted nicely right out of the box!

For more information on querying and validation refer to the [Mongoose documentation](http://mongoosejs.com/docs/guide.html).

## $populate

For Mongoose, the special `$populate` query parameter can be used to allow [Mongoose query population](http://mongoosejs.com/docs/populate.html).

// A basic error handler, just like Express
app.use(errors.handler());
```js
app.service('posts').find({
query: { $populate: 'user' }
});
```

## Discriminators (Inheritance)

Instead of strict inheritance, Mongoose uses [discriminators](http://mongoosejs.com/docs/discriminators.html) as their schema inheritance model.
To use them, pass in a `discriminatorKey` option to your schema object and use `Model.discriminator('modelName', schema)` instead of `mongoose.model()`

Feathers comes with full support for mongoose discriminators, allowing for automatic fetching of inherited types. A typical use case might look like:

```js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var Post = require('./post');
var feathers = require('@feathersjs/feathers');
var app = feathers();
var service = require('feathers-mongoose');

// Discriminator key, we'll use this later to refer to all text posts
var options = {
discriminatorKey: '_type'
};

app.listen(3030);
console.log('Feathers Message mongoose service running on 127.0.0.1:3030');
var TextPostSchema = new Schema({
text: { type: String, default: null }
}, options);

TextPostSchema.index({'updatedAt': -1, background: true});

// Note the use of `Post.discriminator` rather than `mongoose.discriminator`.
var TextPost = Post.discriminator('text', TextPostSchema);

// Using the discriminators option, let feathers know about any inherited models you may have
// for that service
app.use('/posts', service({
Model: Post,
discriminators: [TextPost]
}))

```

Without support for discriminators, when you perform a `.get` on the posts service, you'd only get back `Post` models, not `TextPost` models.
Now in your query, you can specify a value for your discriminatorKey:

```js
{
_type: 'text'
}
```

You can run this example by using `npm start` and going to [localhost:3030/messages](http://localhost:3030/messages). You should see an empty array. That's because you don't have any messages yet but you now have full CRUD for your new message service, including mongoose validations!
and Feathers will automatically swap in the correct model and execute the query it instead of its parent model.

## License

[MIT](LICENSE)

## Authors

- [Feathers contributors](https://github.com/feathersjs/feathers-mongoose/graphs/contributors)
- [Feathers contributors](https://github.com/feathersjs-ecosystem/feathers-mongoose/graphs/contributors)
2 changes: 1 addition & 1 deletion lib/error-handler.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const errors = require('feathers-errors');
const errors = require('@feathersjs/errors');

module.exports = function errorHandler (error) {
if (error.name) {
Expand Down
14 changes: 7 additions & 7 deletions lib/service.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const omit = require('lodash.omit');
const Proto = require('uberproto');
const filter = require('feathers-query-filters');
const { select } = require('feathers-commons');
const errors = require('feathers-errors');

const { select, filterQuery } = require('@feathersjs/commons');
const errors = require('@feathersjs/errors');

const errorHandler = require('./error-handler');

Expand Down Expand Up @@ -36,7 +36,7 @@ class Service {
return Proto.extend(obj, this);
}

_find (params, count, getFilter = filter) {
_find (params, count, getFilter = filterQuery) {
const { filters, query } = getFilter(params.query || {});
const discriminator = (params.query || {})[this.discriminatorKey] || this.discriminatorKey;
const model = this.discriminators[discriminator] || this.Model;
Expand Down Expand Up @@ -107,7 +107,7 @@ class Service {
find (params) {
const paginate = (params && typeof params.paginate !== 'undefined') ? params.paginate : this.paginate;
const result = this._find(params, !!paginate.default,
query => filter(query, paginate)
query => filterQuery(query, paginate)
);

if (!paginate.default) {
Expand Down Expand Up @@ -228,7 +228,7 @@ class Service {
}

patch (id, data, params) {
const query = Object.assign({}, filter(params.query || {}).query);
const query = Object.assign({}, filterQuery(params.query || {}).query);
const mapIds = page => page.data.map(current => current[this.id]);

// By default we will just query for the one id. For multi patch
Expand Down Expand Up @@ -300,7 +300,7 @@ class Service {
}

remove (id, params) {
const query = Object.assign({}, filter(params.query || {}).query);
const query = Object.assign({}, filterQuery(params.query || {}).query);

if (id !== null) {
query[this.id] = id;
Expand Down
1 change: 1 addition & 0 deletions mocha.opts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
--recursive test/
--timeout 5000
--exit
Loading