├── .eslintignore
├── client.js
├── mocha.opts
├── .babelrc
├── .eslintrc.yml
├── .npmignore
├── .codeclimate.yml
├── .editorconfig
├── index.d.ts
├── .istanbul.yml
├── src
├── client
│ ├── axios.js
│ ├── request.js
│ ├── jquery.js
│ ├── superagent.js
│ ├── fetch.js
│ ├── angular.js
│ ├── index.js
│ └── base.js
├── index.js
└── wrappers.js
├── client.d.ts
├── .gitignore
├── .github
├── issue_template.md
├── pull_request_template.md
└── contributing.md
├── .travis.yml
├── LICENSE
├── test
├── client
│ ├── axios.test.js
│ ├── superagent.test.js
│ ├── index.test.js
│ ├── request.test.js
│ ├── server.js
│ ├── fetch.test.js
│ ├── jquery.test.js
│ └── angular.test.js
└── index.test.js
├── README.md
├── package.json
└── CHANGELOG.md
/.eslintignore:
--------------------------------------------------------------------------------
1 | **/*{.,-}min.js
2 |
--------------------------------------------------------------------------------
/client.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./lib/client/index');
2 |
--------------------------------------------------------------------------------
/mocha.opts:
--------------------------------------------------------------------------------
1 | --recursive test/
2 | --compilers js:babel-core/register
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["transform-object-assign", "add-module-exports"],
3 | "presets": [ "es2015" ]
4 | }
5 |
--------------------------------------------------------------------------------
/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | ---
2 | extends: eslint-config-semistandard
3 | env:
4 | es6: true
5 | node: true
6 | mocha: true
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .editorconfig
2 | .travis.yml
3 | .istanbul.yml
4 | .babelrc
5 | .idea/
6 | src/
7 | test/
8 | !lib/
9 | .github/
10 | coverage
--------------------------------------------------------------------------------
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | engines:
2 | duplication:
3 | enabled: true
4 | config:
5 | languages:
6 | - javascript
7 | exclude_paths:
8 | - test/
9 | eslint:
10 | enabled: true
11 | ratings:
12 | paths:
13 | - "**.js"
14 | #exclude_paths:
15 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | import 'express';
2 |
3 | declare function rest(handler?: Formatter): Function;
4 |
5 | declare interface Formatter {
6 | (request: Express.Request, response: Express.Response, next?: Function): void;
7 | }
8 |
9 | declare namespace rest {
10 | const formatter: Formatter;
11 | }
12 |
13 | export = rest;
14 |
--------------------------------------------------------------------------------
/.istanbul.yml:
--------------------------------------------------------------------------------
1 | verbose: false
2 | instrumentation:
3 | root: ./src/
4 | excludes:
5 | - lib/
6 | include-all-sources: true
7 | reporting:
8 | print: summary
9 | reports:
10 | - html
11 | - text
12 | - lcov
13 | watermarks:
14 | statements: [70, 90]
15 | lines: [70, 90]
16 | functions: [70, 90]
17 | branches: [70, 90]
18 |
--------------------------------------------------------------------------------
/src/client/axios.js:
--------------------------------------------------------------------------------
1 | import Base from './base';
2 |
3 | export default class Service extends Base {
4 | request (options) {
5 | const config = {
6 | url: options.url,
7 | method: options.method,
8 | data: options.body,
9 | headers: Object.assign({
10 | Accept: 'application/json'
11 | }, this.options.headers, options.headers)
12 | };
13 |
14 | return this.connection.request(config)
15 | .then(res => res.data)
16 | .catch(error => {
17 | const response = error.response || error;
18 |
19 | throw response instanceof Error ? response : (response.data || response);
20 | });
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/client.d.ts:
--------------------------------------------------------------------------------
1 |
2 | declare function rest(base: string): rest.Transport;
3 |
4 | declare namespace rest {
5 | interface HandlerResult extends Function {
6 | /**
7 | * initialize service
8 | */
9 | (): void;
10 | /**
11 | * Transport Service
12 | */
13 | Service: any;
14 |
15 | /**
16 | * default Service
17 | */
18 | service: any;
19 | }
20 |
21 | interface Handler {
22 | (connection, options?): () => HandlerResult;
23 | }
24 |
25 | interface Transport {
26 | jquery: Handler;
27 | superagent: Handler;
28 | request: Handler;
29 | fetch: Handler;
30 | axios: Handler;
31 | angular: Handler;
32 | }
33 | }
34 |
35 | export = rest;
36 |
--------------------------------------------------------------------------------
/src/client/request.js:
--------------------------------------------------------------------------------
1 | import Base from './base';
2 |
3 | export default class Service extends Base {
4 | request (options) {
5 | return new Promise((resolve, reject) => {
6 | this.connection(Object.assign({
7 | json: true
8 | }, options), function (error, res, data) {
9 | if (error) {
10 | return reject(error);
11 | }
12 |
13 | if (!error && res.statusCode >= 400) {
14 | if (typeof data === 'string') {
15 | return reject(new Error(data));
16 | }
17 |
18 | data.response = res;
19 |
20 | return reject(Object.assign(new Error(data.message), data));
21 | }
22 |
23 | resolve(data);
24 | });
25 | });
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # Compiled binary addons (http://nodejs.org/api/addons.html)
20 | build/Release
21 |
22 | # Dependency directory
23 | # Commenting this out is preferred by some people, see
24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
25 | node_modules
26 |
27 | # Users Environment Variables
28 | .lock-wscript
29 |
30 | # The compiled/babelified modules
31 | lib/
32 |
--------------------------------------------------------------------------------
/.github/issue_template.md:
--------------------------------------------------------------------------------
1 | ### Steps to reproduce
2 |
3 | (First please check that this issue is not already solved as [described
4 | here](https://github.com/feathersjs/feathers/blob/master/.github/contributing.md#report-a-bug))
5 |
6 | - [ ] Tell us what broke. The more detailed the better.
7 | - [ ] If you can, please create a simple example that reproduces the issue and link to a gist, jsbin, repo, etc.
8 |
9 | ### Expected behavior
10 | Tell us what should happen
11 |
12 | ### Actual behavior
13 | Tell us what happens instead
14 |
15 | ### System configuration
16 |
17 | Tell us about the applicable parts of your setup.
18 |
19 | **Module versions** (especially the part that's not working):
20 |
21 | **NodeJS version**:
22 |
23 | **Operating System**:
24 |
25 | **Browser Version**:
26 |
27 | **React Native Version**:
28 |
29 | **Module Loader**:
--------------------------------------------------------------------------------
/src/client/jquery.js:
--------------------------------------------------------------------------------
1 | import Base from './base';
2 |
3 | export default class Service extends Base {
4 | request (options) {
5 | let opts = Object.assign({
6 | dataType: options.type || 'json'
7 | }, {
8 | headers: this.options.headers || {}
9 | }, options);
10 |
11 | if (options.body) {
12 | opts.data = JSON.stringify(options.body);
13 | opts.contentType = 'application/json';
14 | }
15 |
16 | delete opts.type;
17 | delete opts.body;
18 |
19 | return new Promise((resolve, reject) => {
20 | this.connection.ajax(opts).then(resolve, xhr => {
21 | let error = xhr.responseText;
22 |
23 | try {
24 | error = JSON.parse(error);
25 | } catch (e) {
26 | error = new Error(xhr.responseText);
27 | }
28 |
29 | error.xhr = error.response = xhr;
30 |
31 | reject(error);
32 | });
33 | });
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/client/superagent.js:
--------------------------------------------------------------------------------
1 | import Base from './base';
2 |
3 | export default class Service extends Base {
4 | request (options) {
5 | const superagent = this.connection(options.method, options.url)
6 | .set(this.options.headers || {})
7 | .set('Accept', 'application/json')
8 | .set(options.headers || {})
9 | .type(options.type || 'json');
10 |
11 | return new Promise((resolve, reject) => {
12 | superagent.set(options.headers);
13 |
14 | if (options.body) {
15 | superagent.send(options.body);
16 | }
17 |
18 | superagent.end(function (error, res) {
19 | if (error) {
20 | try {
21 | const response = error.response;
22 | error = JSON.parse(error.response.text);
23 | error.response = response;
24 | } catch (e) {}
25 |
26 | return reject(error);
27 | }
28 |
29 | resolve(res && res.body);
30 | });
31 | });
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/client/fetch.js:
--------------------------------------------------------------------------------
1 | import Base from './base';
2 |
3 | export default class Service extends Base {
4 | request (options) {
5 | let fetchOptions = Object.assign({}, options);
6 |
7 | fetchOptions.headers = Object.assign({
8 | Accept: 'application/json'
9 | }, this.options.headers, fetchOptions.headers);
10 |
11 | if (options.body) {
12 | fetchOptions.body = JSON.stringify(options.body);
13 | }
14 |
15 | const fetch = this.connection;
16 |
17 | return fetch(options.url, fetchOptions)
18 | .then(this.checkStatus)
19 | .then(response => {
20 | if (response.status === 204) {
21 | return null;
22 | }
23 |
24 | return response.json();
25 | });
26 | }
27 |
28 | checkStatus (response) {
29 | if (response.ok) {
30 | return response;
31 | }
32 |
33 | return response.json().then(error => {
34 | error.response = response;
35 | throw error;
36 | });
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/client/angular.js:
--------------------------------------------------------------------------------
1 | import Base from './base';
2 |
3 | export default class Service extends Base {
4 | request (options) {
5 | const http = this.connection;
6 | const Headers = this.options.Headers;
7 |
8 | if (!http || !Headers) {
9 | throw new Error(`Please pass angular's 'http' (instance) and and object with 'Headers' (class) to feathers-rest`);
10 | }
11 |
12 | const url = options.url;
13 | const requestOptions = {
14 | method: options.method,
15 | body: options.body,
16 | headers: new Headers(
17 | Object.assign(
18 | { Accept: 'application/json' },
19 | this.options.headers,
20 | options.headers
21 | )
22 | )
23 | };
24 |
25 | return new Promise((resolve, reject) => {
26 | http.request(url, requestOptions).subscribe(resolve, reject);
27 | })
28 | .then(res => res.json())
29 | .catch(error => {
30 | const response = error.response || error;
31 |
32 | throw response instanceof Error ? response : (response.json() || response);
33 | });
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - node
5 | - '6'
6 | - '4'
7 | addons:
8 | code_climate:
9 | repo_token: 81c775b1dd23640f4f3076537995bb1b0c13a6bd5e9a476bbc5b0f4e6997f576
10 | before_script:
11 | - npm install -g codeclimate-test-reporter
12 | after_script:
13 | - codeclimate-test-reporter < coverage/lcov.info
14 | notifications:
15 | email: false
16 | slack:
17 | rooms:
18 | secure: BlkMY8NVmYClqp9jW3EVeBFRsrhNCodDopRp66C885vXbR0prvgAmb6XylaVcik7Y/P+zbbfHkO8VI9HiGmNQgWQOh4hrXIYicQcuXwh0AUfjU9vNaxPq3xguPWkehKql5D2jvBxro8IjVM+yU9GSbpkZ/KZh/o8GniJY6jI6zdS3jIW9vHcuoKKwoH0afrpt8GR1NL7jO8dK5j4hH/0yf9y0Ay2CZw49Gjc/Pj4nZke41ZRbfBzWU/fVCm0PjcyxqUwWrOOoZicWQC9an4Pk8WuESZ7EL2SLSxJ67EZduc5euGf+xxaRwz2BdZ3h5+31cuRYH0Q6LeKcPvyjVjUKoD1vxGg6m/yPZvItjxqu5PJ+/vp4L0GibJDg5qX0JqkaRsT1kc+7oEfW+KsUlW0+R028n1m019Vb66f7mFMsFe8SebRDiNYlQu6CjnYbBXR0sTh6QthGyG7uBktVru+HvsYOpnKRa8ut8fvLY1mrq6WxJZ26Cv2ybB7TPcXZc5mnXe8uddotrbrtjLoAFBK37OAFFWSesGh2T2WJU2Eprugne/iZHNpzLzlP0xIgc3sIGi1jhaKCcgnVFLXCqPp3h6lmM2so5JZumO3CoHI4nd9tdDa+vDG5Zu0YhrTDvxwFiE/poGA6/uguI0td4cFx/8CAB5mIjDKnjnBF7K92mc=
19 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | ### Summary
2 |
3 | (If you have not already please refer to the contributing guideline as [described
4 | here](https://github.com/feathersjs/feathers/blob/master/.github/contributing.md#pull-requests))
5 |
6 | - [ ] Tell us about the problem your pull request is solving.
7 | - [ ] Are there any open issues that are related to this?
8 | - [ ] Is this PR dependent on PRs in other repos?
9 |
10 | If so, please mention them to keep the conversations linked together.
11 |
12 | ### Other Information
13 |
14 | If there's anything else that's important and relevant to your pull
15 | request, mention that information here. This could include
16 | benchmarks, or other information.
17 |
18 | Your PR will be reviewed by a core team member and they will work with you to get your changes merged in a timely manner. If merged your PR will automatically be added to the changelog in the next release.
19 |
20 | If your changes involve documentation updates please mention that and link the appropriate PR in [feathers-docs](https://github.com/feathersjs/feathers-docs).
21 |
22 | Thanks for contributing to Feathers! :heart:
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Feathers
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/src/client/index.js:
--------------------------------------------------------------------------------
1 | import jQuery from './jquery';
2 | import Superagent from './superagent';
3 | import Request from './request';
4 | import Fetch from './fetch';
5 | import Axios from './axios';
6 | import Angular from './angular';
7 |
8 | const transports = {
9 | jquery: jQuery,
10 | superagent: Superagent,
11 | request: Request,
12 | fetch: Fetch,
13 | axios: Axios,
14 | angular: Angular
15 | };
16 |
17 | export default function (base = '') {
18 | const result = {};
19 |
20 | Object.keys(transports).forEach(key => {
21 | const Service = transports[key];
22 |
23 | result[key] = function (connection, options = {}) {
24 | if (!connection) {
25 | throw new Error(`${key} has to be provided to feathers-rest`);
26 | }
27 |
28 | const defaultService = function (name) {
29 | return new Service({ base, name, connection, options });
30 | };
31 |
32 | const initialize = function () {
33 | if (typeof this.defaultService === 'function') {
34 | throw new Error('Only one default client provider can be configured');
35 | }
36 |
37 | this.rest = connection;
38 | this.defaultService = defaultService;
39 | };
40 |
41 | initialize.Service = Service;
42 | initialize.service = defaultService;
43 |
44 | return initialize;
45 | };
46 | });
47 |
48 | return result;
49 | }
50 |
--------------------------------------------------------------------------------
/test/client/axios.test.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import axios from 'axios';
3 | import feathers from 'feathers/client';
4 | import baseTests from 'feathers-commons/lib/test/client';
5 | import errors from 'feathers-errors';
6 |
7 | import server from './server';
8 | import rest from '../../src/client';
9 |
10 | describe('Axios REST connector', function () {
11 | const url = 'http://localhost:8889';
12 | const setup = rest(url).axios(axios);
13 | const app = feathers().configure(setup);
14 | const service = app.service('todos');
15 |
16 | before(function (done) {
17 | this.server = server().listen(8889, done);
18 | });
19 |
20 | after(function (done) {
21 | this.server.close(done);
22 | });
23 |
24 | baseTests(service);
25 |
26 | it('supports custom headers', () => {
27 | let headers = {
28 | 'Authorization': 'let-me-in'
29 | };
30 |
31 | return service.get(0, { headers }).then(todo =>
32 | assert.deepEqual(todo, {
33 | id: 0,
34 | text: 'some todo',
35 | complete: false,
36 | query: {}
37 | })
38 | );
39 | });
40 |
41 | it('can initialize a client instance', () => {
42 | const init = rest(url).axios(axios);
43 | const todos = init.service('todos');
44 |
45 | assert.ok(todos instanceof init.Service, 'Returned service is a client');
46 |
47 | return todos.find({}).then(todos =>
48 | assert.deepEqual(todos, [
49 | {
50 | text: 'some todo',
51 | complete: false,
52 | id: 0
53 | }
54 | ])
55 | );
56 | });
57 |
58 | it('supports nested arrays in queries', () => {
59 | const query = { test: { $in: [ 0, 1, 2 ] } };
60 |
61 | return service.get(0, { query }).then(data =>
62 | assert.deepEqual(data.query, query)
63 | );
64 | });
65 |
66 | it('remove many', () => {
67 | return service.remove(null).then(todo => {
68 | assert.equal(todo.id, null);
69 | assert.equal(todo.text, 'deleted many');
70 | });
71 | });
72 |
73 | it('converts feathers errors (#50)', () => {
74 | return service.get(0, { query: { feathersError: true } })
75 | .catch(error => {
76 | assert.ok(error instanceof errors.NotAcceptable);
77 | assert.equal(error.message, 'This is a Feathers error');
78 | assert.equal(error.code, 406);
79 | });
80 | });
81 | });
82 |
--------------------------------------------------------------------------------
/test/client/superagent.test.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import superagent from 'superagent';
3 | import feathers from 'feathers/client';
4 | import baseTests from 'feathers-commons/lib/test/client';
5 | import errors from 'feathers-errors';
6 |
7 | import server from './server';
8 | import rest from '../../src/client';
9 |
10 | describe('Superagent REST connector', function () {
11 | const url = 'http://localhost:8889';
12 | const setup = rest(url).superagent(superagent);
13 | const app = feathers().configure(setup);
14 | const service = app.service('todos');
15 |
16 | before(function (done) {
17 | this.server = server().listen(8889, done);
18 | });
19 |
20 | after(function (done) {
21 | this.server.close(done);
22 | });
23 |
24 | baseTests(service);
25 |
26 | it('supports custom headers', () => {
27 | let headers = {
28 | 'Authorization': 'let-me-in'
29 | };
30 |
31 | return service.get(0, { headers }).then(todo =>
32 | assert.deepEqual(todo, {
33 | id: 0,
34 | text: 'some todo',
35 | complete: false,
36 | query: {}
37 | })
38 | );
39 | });
40 |
41 | it('can initialize a client instance', () => {
42 | const init = rest(url).superagent(superagent);
43 | const todos = init.service('todos');
44 |
45 | assert.ok(todos instanceof init.Service, 'Returned service is a client');
46 |
47 | return todos.find({}).then(todos =>
48 | assert.deepEqual(todos, [
49 | {
50 | text: 'some todo',
51 | complete: false,
52 | id: 0
53 | }
54 | ])
55 | );
56 | });
57 |
58 | it('supports nested arrays in queries', () => {
59 | const query = { test: { $in: [ 0, 1, 2 ] } };
60 |
61 | return service.get(0, { query }).then(data =>
62 | assert.deepEqual(data.query, query)
63 | );
64 | });
65 |
66 | it('remove many', () => {
67 | return service.remove(null).then(todo => {
68 | assert.equal(todo.id, null);
69 | assert.equal(todo.text, 'deleted many');
70 | });
71 | });
72 |
73 | it('converts feathers errors (#50)', () => {
74 | return service.get(0, { query: { feathersError: true } })
75 | .catch(error => {
76 | assert.ok(error instanceof errors.NotAcceptable);
77 | assert.equal(error.message, 'This is a Feathers error');
78 | assert.equal(error.code, 406);
79 | assert.ok(error.response);
80 | });
81 | });
82 | });
83 |
--------------------------------------------------------------------------------
/test/client/index.test.js:
--------------------------------------------------------------------------------
1 | import fetch from 'node-fetch';
2 | import feathers from 'feathers/client';
3 | import rest from '../../src/client';
4 | import assert from 'assert';
5 |
6 | const init = require('../../client');
7 |
8 | describe('REST client tests', function () {
9 | it('is built correctly', () => {
10 | const transports = init();
11 |
12 | assert.equal(typeof init, 'function');
13 | assert.equal(typeof transports.jquery, 'function');
14 | assert.equal(typeof transports.request, 'function');
15 | assert.equal(typeof transports.superagent, 'function');
16 | assert.equal(typeof transports.fetch, 'function');
17 | });
18 |
19 | it('throw errors when no connection is provided', () => {
20 | const transports = init();
21 |
22 | try {
23 | transports.fetch();
24 | } catch (e) {
25 | assert.equal(e.message, 'fetch has to be provided to feathers-rest');
26 | }
27 | });
28 |
29 | it('app has the rest attribute', () => {
30 | const app = feathers();
31 |
32 | app.configure(rest('http://localhost:8889').fetch(fetch));
33 |
34 | assert.ok(app.rest);
35 | });
36 |
37 | it('throws an error when configured twice', () => {
38 | const app = feathers();
39 |
40 | app.configure(rest('http://localhost:8889').fetch(fetch));
41 |
42 | try {
43 | app.configure(rest('http://localhost:8889').fetch(fetch));
44 | assert.ok(false, 'Should never get here');
45 | } catch (e) {
46 | assert.equal(e.message, 'Only one default client provider can be configured');
47 | }
48 | });
49 |
50 | it('errors when id property for get, patch, update or remove is undefined', () => {
51 | const app = feathers().configure(rest('http://localhost:8889')
52 | .fetch(fetch));
53 |
54 | const service = app.service('todos');
55 |
56 | return service.get().catch(error => {
57 | assert.equal(error.message, `id for 'get' can not be undefined`);
58 |
59 | return service.remove();
60 | }).catch(error => {
61 | assert.equal(error.message, `id for 'remove' can not be undefined, only 'null' when removing multiple entries`);
62 |
63 | return service.update(undefined, {});
64 | }).catch(error => {
65 | assert.equal(error.message, `id for 'update' can not be undefined, only 'null' when updating multiple entries`);
66 |
67 | return service.patch(undefined, {});
68 | }).catch(error => {
69 | assert.equal(error.message, `id for 'patch' can not be undefined, only 'null' when updating multiple entries`);
70 | });
71 | });
72 | });
73 |
--------------------------------------------------------------------------------
/test/client/request.test.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import request from 'request';
3 | import feathers from 'feathers/client';
4 | import baseTests from 'feathers-commons/lib/test/client';
5 | import errors from 'feathers-errors';
6 |
7 | import server from './server';
8 | import rest from '../../src/client';
9 |
10 | describe('node-request REST connector', function () {
11 | const url = 'http://localhost:6777';
12 | const setup = rest(url).request(request);
13 | const app = feathers().configure(setup);
14 | const service = app.service('todos');
15 |
16 | before(function (done) {
17 | this.server = server().listen(6777, done);
18 | });
19 |
20 | after(function (done) {
21 | this.server.close(done);
22 | });
23 |
24 | baseTests(service);
25 |
26 | it('supports custom headers', () => {
27 | let headers = {
28 | 'Authorization': 'let-me-in'
29 | };
30 |
31 | return service.get(0, { headers }).then(todo =>
32 | assert.deepEqual(todo, {
33 | id: 0,
34 | text: 'some todo',
35 | complete: false,
36 | query: {}
37 | })
38 | );
39 | });
40 |
41 | it('can initialize a client instance', () => {
42 | const init = rest(url).request(request);
43 | const todos = init.service('todos');
44 |
45 | assert.ok(todos instanceof init.Service, 'Returned service is a client');
46 |
47 | return todos.find({}).then(todos =>
48 | assert.deepEqual(todos, [
49 | {
50 | text: 'some todo',
51 | complete: false,
52 | id: 0
53 | }
54 | ])
55 | );
56 | });
57 |
58 | it('supports nested arrays in queries', () => {
59 | const query = { test: { $in: [ 0, 1, 2 ] } };
60 |
61 | return service.get(0, { query }).then(data =>
62 | assert.deepEqual(data.query, query)
63 | );
64 | });
65 |
66 | it('converts errors properly', () => {
67 | return service.get(1, { query: { error: true } }).catch(e =>
68 | assert.equal(e.message, 'Something went wrong')
69 | );
70 | });
71 |
72 | it('remove many', () => {
73 | return service.remove(null).then(todo => {
74 | assert.equal(todo.id, null);
75 | assert.equal(todo.text, 'deleted many');
76 | });
77 | });
78 |
79 | it('converts feathers errors (#50)', () => {
80 | return service.get(0, { query: { feathersError: true } }).catch(error => {
81 | assert.ok(error instanceof errors.NotAcceptable);
82 | assert.equal(error.message, 'This is a Feathers error');
83 | assert.equal(error.code, 406);
84 | assert.deepEqual(error.data, { data: true });
85 | assert.ok(error.response);
86 | });
87 | });
88 | });
89 |
--------------------------------------------------------------------------------
/test/client/server.js:
--------------------------------------------------------------------------------
1 | import feathers from 'feathers';
2 | import bodyParser from 'body-parser';
3 | import memory from 'feathers-memory';
4 | import errors from 'feathers-errors';
5 | import rest from '../../src';
6 |
7 | // eslint-disable-next-line no-extend-native
8 | Object.defineProperty(Error.prototype, 'toJSON', {
9 | value: function () {
10 | var alt = {};
11 |
12 | Object.getOwnPropertyNames(this).forEach(function (key) {
13 | alt[key] = this[key];
14 | }, this);
15 |
16 | return alt;
17 | },
18 | configurable: true,
19 | writable: true
20 | });
21 |
22 | let errorHandler = function (error, req, res, next) {
23 | const code = !isNaN(parseInt(error.code, 10)) ? parseInt(error.code, 10) : 500;
24 | res.status(code);
25 |
26 | res.format({
27 | 'application/json': function () {
28 | let output = Object.assign({}, error.toJSON());
29 | res.json(output);
30 | }
31 | });
32 | };
33 |
34 | module.exports = function (configurer) {
35 | // Create an in-memory CRUD service for our Todos
36 | var todoService = memory().extend({
37 | get (id, params) {
38 | if (params.query.error) {
39 | throw new Error('Something went wrong');
40 | }
41 |
42 | if (params.query.feathersError) {
43 | throw new errors.NotAcceptable('This is a Feathers error', { data: true });
44 | }
45 |
46 | return this._super(id, params)
47 | .then(data => Object.assign({ query: params.query }, data));
48 | },
49 |
50 | remove (id, params) {
51 | if (id === null) {
52 | return Promise.resolve({
53 | id, text: 'deleted many'
54 | });
55 | }
56 |
57 | if (params.query.noContent) {
58 | return Promise.resolve();
59 | }
60 |
61 | return this._super.apply(this, arguments);
62 | }
63 | });
64 |
65 | var app = feathers()
66 | .configure(rest(function formatter (req, res, next) {
67 | if (!res.data) {
68 | next();
69 | }
70 |
71 | res.format({
72 | html () {
73 | res.end('
This is HTML content. You should not see it.
');
74 | },
75 |
76 | json () {
77 | res.json(res.data);
78 | }
79 | });
80 | }))
81 | // Parse HTTP bodies
82 | .use(bodyParser.json())
83 | .use(bodyParser.urlencoded({ extended: true }))
84 | // Host the current directory (for index.html)
85 | .use(feathers.static(__dirname))
86 | // Host our Todos service on the /todos path
87 | .use('/todos', todoService)
88 | .use(errorHandler);
89 |
90 | if (typeof configurer === 'function') {
91 | configurer.call(app);
92 | }
93 |
94 | app.service('todos').create({ text: 'some todo', complete: false }, {}, function () {});
95 |
96 | return app;
97 | };
98 |
--------------------------------------------------------------------------------
/test/client/fetch.test.js:
--------------------------------------------------------------------------------
1 | import fetch from 'node-fetch';
2 | import assert from 'assert';
3 | import feathers from 'feathers/client';
4 | import errors from 'feathers-errors';
5 | import baseTests from 'feathers-commons/lib/test/client';
6 |
7 | import server from './server';
8 | import rest from '../../src/client';
9 |
10 | describe('fetch REST connector', function () {
11 | const url = 'http://localhost:8889';
12 | const setup = rest(url).fetch(fetch);
13 | const app = feathers().configure(setup);
14 | const service = app.service('todos');
15 |
16 | before(function (done) {
17 | this.server = server().listen(8889, done);
18 | });
19 |
20 | after(function (done) {
21 | this.server.close(done);
22 | });
23 |
24 | baseTests(service);
25 |
26 | it('supports custom headers', () => {
27 | let headers = {
28 | 'Authorization': 'let-me-in'
29 | };
30 |
31 | return service.get(0, { headers }).then(todo =>
32 | assert.deepEqual(todo, {
33 | id: 0,
34 | text: 'some todo',
35 | complete: false,
36 | query: {}
37 | })
38 | );
39 | });
40 |
41 | it('handles errors properly', () => {
42 | return service.get(-1, {}).catch(error =>
43 | assert.equal(error.code, 404)
44 | );
45 | });
46 |
47 | it('supports nested arrays in queries', () => {
48 | const query = { test: { $in: [ 0, 1, 2 ] } };
49 |
50 | return service.get(0, { query }).then(data =>
51 | assert.deepEqual(data.query, query)
52 | );
53 | });
54 |
55 | it('can initialize a client instance', () => {
56 | const init = rest(url).fetch(fetch);
57 | const todos = init.service('todos');
58 |
59 | assert.ok(todos instanceof init.Service, 'Returned service is a client');
60 |
61 | return todos.find({}).then(todos =>
62 | assert.deepEqual(todos, [
63 | {
64 | text: 'some todo',
65 | complete: false,
66 | id: 0
67 | }
68 | ])
69 | );
70 | });
71 |
72 | it('remove many', () => {
73 | return service.remove(null).then(todo => {
74 | assert.equal(todo.id, null);
75 | assert.equal(todo.text, 'deleted many');
76 | });
77 | });
78 |
79 | it('converts feathers errors (#50)', () => {
80 | return service.get(0, { query: { feathersError: true } }).catch(error => {
81 | assert.ok(error instanceof errors.NotAcceptable);
82 | assert.equal(error.message, 'This is a Feathers error');
83 | assert.equal(error.code, 406);
84 | assert.deepEqual(error.data, { data: true });
85 | assert.ok(error.response);
86 | });
87 | });
88 |
89 | it('returns null for 204 responses', () => {
90 | return service.remove(0, { query: { noContent: true } })
91 | .then(response =>
92 | assert.strictEqual(response, null)
93 | );
94 | });
95 | });
96 |
--------------------------------------------------------------------------------
/test/client/jquery.test.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import feathers from 'feathers';
3 | import jsdom from 'jsdom';
4 | import baseTests from 'feathers-commons/lib/test/client';
5 | import errors from 'feathers-errors';
6 |
7 | import server from './server';
8 | import rest from '../../src/client';
9 |
10 | describe('jQuery REST connector', function () {
11 | const url = 'http://localhost:7676';
12 | const setup = rest(url).jquery({});
13 | const app = feathers().configure(setup);
14 | const service = app.service('todos');
15 |
16 | before(function (done) {
17 | this.server = server().listen(7676, function () {
18 | jsdom.env(
19 | '',
20 | [ 'http://code.jquery.com/jquery-2.1.4.js' ],
21 | function (err, window) { // eslint-disable-line handle-callback-err
22 | window.jQuery.support.cors = true;
23 | service.connection = window.jQuery;
24 | done();
25 | }
26 | );
27 | });
28 | });
29 |
30 | after(function () {
31 | this.server.close();
32 | });
33 |
34 | baseTests(service);
35 |
36 | it('supports custom headers', () => {
37 | let headers = {
38 | 'Authorization': 'let-me-in'
39 | };
40 |
41 | return service.get(0, { headers }).then(todo =>
42 | assert.deepEqual(todo, {
43 | id: 0,
44 | text: 'some todo',
45 | complete: false,
46 | query: {}
47 | })
48 | );
49 | });
50 |
51 | it('can initialize a client instance', () => {
52 | const init = rest(url).jquery(service.connection);
53 | const todos = init.service('todos');
54 |
55 | assert.ok(todos instanceof init.Service, 'Returned service is a client');
56 |
57 | return todos.find({}).then(todos =>
58 | assert.deepEqual(todos, [
59 | {
60 | text: 'some todo',
61 | complete: false,
62 | id: 0
63 | }
64 | ])
65 | );
66 | });
67 |
68 | it('supports nested arrays in queries', () => {
69 | const query = { test: { $in: [ 0, 1, 2 ] } };
70 |
71 | return service.get(0, { query }).then(data =>
72 | assert.deepEqual(data.query, query)
73 | );
74 | });
75 |
76 | it('remove many', () => {
77 | return service.remove(null).then(todo => {
78 | assert.equal(todo.id, null);
79 | assert.equal(todo.text, 'deleted many');
80 | });
81 | });
82 |
83 | it('converts feathers errors (#50)', () => {
84 | return service.get(0, { query: { feathersError: true } })
85 | .catch(error => {
86 | assert.ok(error instanceof errors.NotAcceptable);
87 | assert.equal(error.message, 'This is a Feathers error');
88 | assert.equal(error.code, 406);
89 | assert.deepEqual(error.data, { data: true });
90 | assert.ok(error.response);
91 | });
92 | });
93 | });
94 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import makeDebug from 'debug';
2 | import wrappers from './wrappers';
3 |
4 | const debug = makeDebug('feathers-rest');
5 |
6 | function formatter (req, res, next) {
7 | if (res.data === undefined) {
8 | return next();
9 | }
10 |
11 | res.format({
12 | 'application/json': function () {
13 | res.json(res.data);
14 | }
15 | });
16 | }
17 |
18 | export default function rest (handler = formatter) {
19 | return function () {
20 | const app = this;
21 |
22 | if (app.version && app.version >= '3.0.0') {
23 | throw new Error(`feathers-rest is not compatible with Feathers v${app.version}. Use the Express framework bindings and REST adapter at @feathersjs/express instead.`);
24 | }
25 |
26 | app.use(function (req, res, next) {
27 | req.feathers = { provider: 'rest' };
28 | next();
29 | });
30 |
31 | app.rest = wrappers;
32 |
33 | // Register the REST provider
34 | app.providers.push(function (path, service, options) {
35 | const uri = path.indexOf('/') === 0 ? path : `/${path}`;
36 | const baseRoute = app.route(uri);
37 | const idRoute = app.route(`${uri}/:__feathersId`);
38 |
39 | let middleware = (options || {}).middleware || {};
40 | let before = middleware.before || [];
41 | let after = middleware.after || [];
42 |
43 | if (typeof handler === 'function') {
44 | after = after.concat(handler);
45 | }
46 |
47 | debug(`Adding REST provider for service \`${path}\` at base route \`${uri}\``);
48 |
49 | // GET / -> service.find(cb, params)
50 | baseRoute.get.apply(baseRoute, before.concat(app.rest.find(service), after));
51 | // POST / -> service.create(data, params, cb)
52 | baseRoute.post.apply(baseRoute, before.concat(app.rest.create(service), after));
53 | // PATCH / -> service.patch(null, data, params)
54 | baseRoute.patch.apply(baseRoute, before.concat(app.rest.patch(service), after));
55 | // PUT / -> service.update(null, data, params)
56 | baseRoute.put.apply(baseRoute, before.concat(app.rest.update(service), after));
57 | // DELETE / -> service.remove(null, params)
58 | baseRoute.delete.apply(baseRoute, before.concat(app.rest.remove(service), after));
59 |
60 | // GET /:id -> service.get(id, params, cb)
61 | idRoute.get.apply(idRoute, before.concat(app.rest.get(service), after));
62 | // PUT /:id -> service.update(id, data, params, cb)
63 | idRoute.put.apply(idRoute, before.concat(app.rest.update(service), after));
64 | // PATCH /:id -> service.patch(id, data, params, callback)
65 | idRoute.patch.apply(idRoute, before.concat(app.rest.patch(service), after));
66 | // DELETE /:id -> service.remove(id, params, cb)
67 | idRoute.delete.apply(idRoute, before.concat(app.rest.remove(service), after));
68 | });
69 | };
70 | }
71 |
72 | rest.formatter = formatter;
73 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # feathers-rest
2 |
3 | > _Important:_ For Feathers v3.0.0 and later, `feathers-rest` is has been replaced by framework specific adapters. Use [`@feathersjs/express`](https://github.com/feathersjs/express) and its REST adapter instead of this module.
4 |
5 | [](https://greenkeeper.io/)
6 |
7 | [](https://travis-ci.org/feathersjs-ecosystem/feathers-rest)
8 | [](https://david-dm.org/feathersjs-ecosystem/feathers-rest)
9 | [](https://www.npmjs.com/package/feathers-rest)
10 |
11 | > The Feathers REST API provider
12 |
13 | ## About
14 |
15 | This provider exposes [Feathers](http://feathersjs.com) services through a RESTful API using [Express](http://expressjs.com) that can be used with Feathers 1.x and 2.x as well as client support for Fetch, jQuery, Request, Superagent, axios and angular2+'s HTTP Service.
16 |
17 | __Note:__ For the full API documentation go to [https://docs.feathersjs.com/api/rest.html](https://docs.feathersjs.com/api/rest.html).
18 |
19 | ## Quick example
20 |
21 | ```js
22 | import feathers from 'feathers';
23 | import bodyParser from 'body-parser';
24 | import rest from 'feathers-rest';
25 |
26 | const app = feathers()
27 | .configure(rest())
28 | .use(bodyParser.json())
29 | .use(bodyParser.urlencoded({ extended: true }))
30 | .use(function(req, res, next) {
31 | req.feathers.data = 'Hello world';
32 | next();
33 | });
34 |
35 | app.use('/:app/todos', {
36 | get: function(id, params) {
37 | console.log(params.data); // -> 'Hello world'
38 | console.log(params.app); // will be `my` for GET /my/todos/dishes
39 |
40 | return Promise.resolve({
41 | id,
42 | params,
43 | description: `You have to do ${name}!`
44 | });
45 | }
46 | });
47 | ```
48 |
49 | ## Client use
50 |
51 | ```js
52 | import feathers from 'feathers/client';
53 | import rest from 'feathers-rest/client';
54 |
55 | import jQuery from 'jquery';
56 | import request from 'request';
57 | import superagent from 'superagent';
58 | import axios from 'axios';
59 | import {Http, Headers} from '@angular/http';
60 |
61 |
62 | const app = feathers()
63 | .configure(rest('http://baseUrl').jquery(jQuery))
64 | // or
65 | .configure(rest('http://baseUrl').fetch(window.fetch.bind(window)))
66 | // or
67 | .configure(rest('http://baseUrl').request(request))
68 | // or
69 | .configure(rest('http://baseUrl').superagent(superagent))
70 | // or
71 | .configure(rest('http://baseUrl').axios(axios))
72 | // or (using injected Http instance)
73 | .configure(rest('http://baseUrl').angular(http, { Headers }))
74 | ```
75 |
76 | ## License
77 |
78 | Copyright (c) 2015
79 |
80 | Licensed under the [MIT license](LICENSE).
81 |
--------------------------------------------------------------------------------
/test/client/angular.test.js:
--------------------------------------------------------------------------------
1 | import assert from 'assert';
2 | import feathers from 'feathers/client';
3 | import baseTests from 'feathers-commons/lib/test/client';
4 | import errors from 'feathers-errors';
5 |
6 | import server from './server';
7 | import rest from '../../src/client';
8 |
9 | import {
10 | Http,
11 | Headers,
12 | BaseRequestOptions,
13 | XHRBackend
14 | } from '@angular/http';
15 |
16 | const xhr2 = require('xhr2');
17 |
18 | function createAngularHTTP () {
19 | const XSRFStrategyShim = { configureRequest: () => {} };
20 | const serverXHR = { build: () => new xhr2.XMLHttpRequest() };
21 |
22 | return new Http(
23 | new XHRBackend(
24 | serverXHR,
25 | new BaseRequestOptions(),
26 | XSRFStrategyShim
27 | ),
28 | new BaseRequestOptions()
29 | );
30 | }
31 |
32 | describe('angular REST connector', function () {
33 | const angularHttp = createAngularHTTP();
34 |
35 | const url = 'http://localhost:8889';
36 | const setup = rest(url).angular(angularHttp, {Headers});
37 | const app = feathers().configure(setup);
38 | const service = app.service('todos');
39 |
40 | before(function (done) {
41 | this.server = server().listen(8889, done);
42 | });
43 |
44 | after(function (done) {
45 | this.server.close(done);
46 | });
47 |
48 | baseTests(service);
49 |
50 | it('supports custom headers', () => {
51 | let headers = {
52 | 'Authorization': 'let-me-in'
53 | };
54 |
55 | return service.get(0, {headers}).then(todo =>
56 | assert.deepEqual(todo, {
57 | id: 0,
58 | text: 'some todo',
59 | complete: false,
60 | query: {}
61 | })
62 | );
63 | });
64 |
65 | it('can initialize a client instance', () => {
66 | const init = rest(url).angular(angularHttp, {Headers});
67 | const todos = init.service('todos');
68 |
69 | assert.ok(todos instanceof init.Service, 'Returned service is a client');
70 |
71 | return todos.find({}).then(todos =>
72 | assert.deepEqual(todos, [
73 | {
74 | text: 'some todo',
75 | complete: false,
76 | id: 0
77 | }
78 | ])
79 | );
80 | });
81 |
82 | it('supports nested arrays in queries', () => {
83 | const query = {test: {$in: [0, 1, 2]}};
84 |
85 | return service.get(0, {query}).then(data =>
86 | assert.deepEqual(data.query, query)
87 | );
88 | });
89 |
90 | it('remove many', () => {
91 | return service.remove(null).then(todo => {
92 | assert.equal(todo.id, null);
93 | assert.equal(todo.text, 'deleted many');
94 | });
95 | });
96 |
97 | it('converts feathers errors (#50)', () => {
98 | return service.get(0, {query: {feathersError: true}})
99 | .catch(error => {
100 | assert.ok(error instanceof errors.NotAcceptable);
101 | assert.equal(error.message, 'This is a Feathers error');
102 | assert.equal(error.code, 406);
103 | });
104 | });
105 | });
106 |
--------------------------------------------------------------------------------
/src/client/base.js:
--------------------------------------------------------------------------------
1 | import query from 'qs';
2 | import { stripSlashes } from 'feathers-commons';
3 | import { convert } from 'feathers-errors';
4 |
5 | function toError (error) {
6 | throw convert(error);
7 | }
8 |
9 | export default class Base {
10 | constructor (settings) {
11 | this.name = stripSlashes(settings.name);
12 | this.options = settings.options;
13 | this.connection = settings.connection;
14 | this.base = `${settings.base}/${this.name}`;
15 | }
16 |
17 | makeUrl (params, id) {
18 | params = params || {};
19 | let url = this.base;
20 |
21 | if (typeof id !== 'undefined' && id !== null) {
22 | url += `/${id}`;
23 | }
24 |
25 | if (Object.keys(params).length !== 0) {
26 | const queryString = query.stringify(params);
27 |
28 | url += `?${queryString}`;
29 | }
30 |
31 | return url;
32 | }
33 |
34 | find (params = {}) {
35 | return this.request({
36 | url: this.makeUrl(params.query),
37 | method: 'GET',
38 | headers: Object.assign({}, params.headers)
39 | }).catch(toError);
40 | }
41 |
42 | get (id, params = {}) {
43 | if (typeof id === 'undefined') {
44 | return Promise.reject(new Error(`id for 'get' can not be undefined`));
45 | }
46 |
47 | return this.request({
48 | url: this.makeUrl(params.query, id),
49 | method: 'GET',
50 | headers: Object.assign({}, params.headers)
51 | }).catch(toError);
52 | }
53 |
54 | create (body, params = {}) {
55 | return this.request({
56 | url: this.makeUrl(params.query),
57 | body,
58 | method: 'POST',
59 | headers: Object.assign({ 'Content-Type': 'application/json' }, params.headers)
60 | }).catch(toError);
61 | }
62 |
63 | update (id, body, params = {}) {
64 | if (typeof id === 'undefined') {
65 | return Promise.reject(new Error(`id for 'update' can not be undefined, only 'null' when updating multiple entries`));
66 | }
67 |
68 | return this.request({
69 | url: this.makeUrl(params.query, id),
70 | body,
71 | method: 'PUT',
72 | headers: Object.assign({ 'Content-Type': 'application/json' }, params.headers)
73 | }).catch(toError);
74 | }
75 |
76 | patch (id, body, params = {}) {
77 | if (typeof id === 'undefined') {
78 | return Promise.reject(new Error(`id for 'patch' can not be undefined, only 'null' when updating multiple entries`));
79 | }
80 |
81 | return this.request({
82 | url: this.makeUrl(params.query, id),
83 | body,
84 | method: 'PATCH',
85 | headers: Object.assign({ 'Content-Type': 'application/json' }, params.headers)
86 | }).catch(toError);
87 | }
88 |
89 | remove (id, params = {}) {
90 | if (typeof id === 'undefined') {
91 | return Promise.reject(new Error(`id for 'remove' can not be undefined, only 'null' when removing multiple entries`));
92 | }
93 |
94 | return this.request({
95 | url: this.makeUrl(params.query, id),
96 | method: 'DELETE',
97 | headers: Object.assign({}, params.headers)
98 | }).catch(toError);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "feathers-rest",
3 | "description": "The Feathers REST API provider",
4 | "version": "1.8.1",
5 | "homepage": "https://github.com/feathersjs-ecosystem/feathers-rest",
6 | "main": "lib/",
7 | "types": [
8 | "./index.d.ts",
9 | "./client.d.ts"
10 | ],
11 | "keywords": [
12 | "feathers",
13 | "feathers-plugin"
14 | ],
15 | "license": "MIT",
16 | "repository": {
17 | "type": "git",
18 | "url": "git://github.com/feathersjs-ecosystem/feathers-rest.git"
19 | },
20 | "author": {
21 | "name": "Feathers contributors",
22 | "email": "hello@feathersjs.com",
23 | "url": "https://feathersjs.com"
24 | },
25 | "contributors": [],
26 | "bugs": {
27 | "url": "https://github.com/feathersjs-ecosystem/feathers-rest/issues"
28 | },
29 | "engines": {
30 | "node": ">= 4"
31 | },
32 | "scripts": {
33 | "prepublish": "npm run compile",
34 | "publish": "git push origin --tags && npm run changelog && git push origin",
35 | "changelog": "github_changelog_generator && git add CHANGELOG.md && git commit -am \"Updating changelog\"",
36 | "release:patch": "npm version patch && npm publish",
37 | "release:minor": "npm version minor && npm publish",
38 | "release:major": "npm version major && npm publish",
39 | "compile": "rimraf -rf lib/ && babel -d lib/ src/",
40 | "watch": "babel --watch -d lib/ src/",
41 | "lint": "eslint-if-supported semistandard --fix",
42 | "mocha": "mocha --opts mocha.opts",
43 | "coverage": "istanbul cover node_modules/mocha/bin/_mocha -- --opts mocha.opts",
44 | "test": "npm run compile && npm run lint && npm run coverage"
45 | },
46 | "semistandard": {
47 | "env": [
48 | "mocha"
49 | ],
50 | "ignore": [
51 | "/lib"
52 | ]
53 | },
54 | "directories": {
55 | "lib": "lib"
56 | },
57 | "browser": {
58 | "./lib/index": "./lib/client/index"
59 | },
60 | "greenkeeper": {
61 | "ignore": "jsdom"
62 | },
63 | "dependencies": {
64 | "debug": "^3.1.0",
65 | "feathers-commons": "^0.8.0",
66 | "feathers-errors": "^2.0.1",
67 | "qs": "^6.4.0"
68 | },
69 | "greenkeeper": {
70 | "ignore": [
71 | "@angular/common",
72 | "@angular/core",
73 | "@angular/http",
74 | "@angular/platform-browser"
75 | ]
76 | },
77 | "devDependencies": {
78 | "@angular/common": "^4.2.5",
79 | "@angular/core": "^4.2.5",
80 | "@angular/http": "^4.2.5",
81 | "@angular/platform-browser": "^4.2.5",
82 | "@feathersjs/feathers": "^3.0.0-pre.3",
83 | "axios": "^0.17.0",
84 | "babel-cli": "^6.3.17",
85 | "babel-core": "^6.3.26",
86 | "babel-plugin-add-module-exports": "^0.2.0",
87 | "babel-plugin-transform-object-assign": "^6.3.13",
88 | "babel-preset-es2015": "^6.3.13",
89 | "body-parser": "^1.14.2",
90 | "eslint-if-supported": "^1.0.1",
91 | "feathers": "^2.0.0-pre.1",
92 | "feathers-memory": "^1.0.0",
93 | "istanbul": "^1.1.0-alpha.1",
94 | "jsdom": "8.1.0",
95 | "mocha": "^4.0.0",
96 | "node-fetch": "^1.3.3",
97 | "request": "^2.67.0",
98 | "rimraf": "^2.5.4",
99 | "rxjs": "^5.4.1",
100 | "semistandard": "^11.0.0",
101 | "superagent": "^3.0.0",
102 | "xhr2": "^0.1.4"
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/wrappers.js:
--------------------------------------------------------------------------------
1 | import makeDebug from 'debug';
2 | import errors from 'feathers-errors';
3 | import { hooks } from 'feathers-commons';
4 |
5 | const debug = makeDebug('feathers:rest');
6 | const hookObject = hooks.hookObject;
7 | const statusCodes = {
8 | created: 201,
9 | noContent: 204,
10 | methodNotAllowed: 405
11 | };
12 | const methodMap = {
13 | find: 'GET',
14 | get: 'GET',
15 | create: 'POST',
16 | update: 'PUT',
17 | patch: 'PATCH',
18 | remove: 'DELETE'
19 | };
20 | const allowedMethods = function (service) {
21 | return Object.keys(methodMap)
22 | .filter(method => typeof service[method] === 'function')
23 | .map(method => methodMap[method])
24 | // Filter out duplicates
25 | .filter((value, index, list) => list.indexOf(value) === index);
26 | };
27 |
28 | // A function that returns the middleware for a given method and service
29 | // `getArgs` is a function that should return additional leading service arguments
30 | function getHandler (method, getArgs, service) {
31 | return function (req, res, next) {
32 | res.setHeader('Allow', allowedMethods(service).join(','));
33 |
34 | // Check if the method exists on the service at all. Send 405 (Method not allowed) if not
35 | if (typeof service[method] !== 'function') {
36 | debug(`Method '${method}' not allowed on '${req.url}'`);
37 | res.status(statusCodes.methodNotAllowed);
38 | return next(new errors.MethodNotAllowed(`Method \`${method}\` is not supported by this endpoint.`));
39 | }
40 |
41 | let params = Object.assign({}, req.params || {});
42 | delete params.__feathersId;
43 |
44 | // Grab the service parameters. Use req.feathers and set the query to req.query
45 | params = Object.assign({ query: req.query || {} }, params, req.feathers);
46 |
47 | // Run the getArgs callback, if available, for additional parameters
48 | const args = getArgs(req, res, next);
49 |
50 | // The service success callback which sets res.data or calls next() with the error
51 | const callback = function (error, data) {
52 | const hookArgs = args.concat([ params, callback ]);
53 |
54 | if (error) {
55 | debug(`Error in REST handler: \`${error.message || error}\``);
56 | res.hook = hookObject(method, 'error', hookArgs);
57 | return next(error);
58 | }
59 |
60 | res.data = data;
61 | res.hook = hookObject(method, 'after', hookArgs);
62 |
63 | if (!data) {
64 | debug(`No content returned for '${req.url}'`);
65 | res.status(statusCodes.noContent);
66 | } else if (method === 'create') {
67 | res.status(statusCodes.created);
68 | }
69 |
70 | return next();
71 | };
72 |
73 | debug(`REST handler calling \`${method}\` from \`${req.url}\``);
74 | service[method].apply(service, args.concat([ params, callback ]));
75 | };
76 | }
77 |
78 | // Returns no leading parameters
79 | function reqNone () {
80 | return [];
81 | }
82 |
83 | // Returns the leading parameters for a `get` or `remove` request (the id)
84 | function reqId (req) {
85 | return [ req.params.__feathersId || null ];
86 | }
87 |
88 | // Returns the leading parameters for an `update` or `patch` request (id, data)
89 | function reqUpdate (req) {
90 | return [ req.params.__feathersId || null, req.body ];
91 | }
92 |
93 | // Returns the leading parameters for a `create` request (data)
94 | function reqCreate (req) {
95 | return [ req.body ];
96 | }
97 |
98 | // Returns wrapped middleware for a service method.
99 | // Doing some fancy ES 5 .bind argument currying for .getHandler()
100 | // Basically what you are getting for each is a function(service) {}
101 | export default {
102 | find: getHandler.bind(null, 'find', reqNone),
103 | get: getHandler.bind(null, 'get', reqId),
104 | create: getHandler.bind(null, 'create', reqCreate),
105 | update: getHandler.bind(null, 'update', reqUpdate),
106 | patch: getHandler.bind(null, 'patch', reqUpdate),
107 | remove: getHandler.bind(null, 'remove', reqId)
108 | };
109 |
--------------------------------------------------------------------------------
/.github/contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing to Feathers
2 |
3 | Thank you for contributing to Feathers! :heart: :tada:
4 |
5 | This repo is the main core and where most issues are reported. Feathers embraces modularity and is broken up across many repos. To make this easier to manage we currently use [Zenhub](https://www.zenhub.com/) for issue triage and visibility. They have a free browser plugin you can install so that you can see what is in flight at any time, but of course you also always see current issues in Github.
6 |
7 | ## Report a bug
8 |
9 | Before creating an issue please make sure you have checked out the docs, specifically the [FAQ](https://docs.feathersjs.com/help/faq.html) section. You might want to also try searching Github. It's pretty likely someone has already asked a similar question.
10 |
11 | If you haven't found your answer please feel free to join our [slack channel](http://slack.feathersjs.com), create an issue on Github, or post on [Stackoverflow](http://stackoverflow.com) using the `feathers` or `feathersjs` tag. We try our best to monitor Stackoverflow but you're likely to get more immediate responses in Slack and Github.
12 |
13 | Issues can be reported in the [issue tracker](https://github.com/feathersjs/feathers/issues). Since feathers combines many modules it can be hard for us to assess the root cause without knowing which modules are being used and what your configuration looks like, so **it helps us immensely if you can link to a simple example that reproduces your issue**.
14 |
15 | ## Report a Security Concern
16 |
17 | We take security very seriously at Feathers. We welcome any peer review of our 100% open source code to ensure nobody's Feathers app is ever compromised or hacked. As a web application developer you are responsible for any security breaches. We do our very best to make sure Feathers is as secure as possible by default.
18 |
19 | In order to give the community time to respond and upgrade we strongly urge you report all security issues to us. Send one of the core team members a PM in [Slack](http://slack.feathersjs.com) or email us at hello@feathersjs.com with details and we will respond ASAP.
20 |
21 | For full details refer to our [Security docs](https://docs.feathersjs.com/SECURITY.html).
22 |
23 | ## Pull Requests
24 |
25 | We :heart: pull requests and we're continually working to make it as easy as possible for people to contribute, including a [Plugin Generator](https://github.com/feathersjs/generator-feathers-plugin) and a [common test suite](https://github.com/feathersjs/feathers-service-tests) for database adapters.
26 |
27 | We prefer small pull requests with minimal code changes. The smaller they are the easier they are to review and merge. A core team member will pick up your PR and review it as soon as they can. They may ask for changes or reject your pull request. This is not a reflection of you as an engineer or a person. Please accept feedback graciously as we will also try to be sensitive when providing it.
28 |
29 | Although we generally accept many PRs they can be rejected for many reasons. We will be as transparent as possible but it may simply be that you do not have the same context or information regarding the roadmap that the core team members have. We value the time you take to put together any contributions so we pledge to always be respectful of that time and will try to be as open as possible so that you don't waste it. :smile:
30 |
31 | **All PRs (except documentation) should be accompanied with tests and pass the linting rules.**
32 |
33 | ### Code style
34 |
35 | Before running the tests from the `test/` folder `npm test` will run ESlint. You can check your code changes individually by running `npm run lint`.
36 |
37 | ### ES6 compilation
38 |
39 | Feathers uses [Babel](https://babeljs.io/) to leverage the latest developments of the JavaScript language. All code and samples are currently written in ES2015. To transpile the code in this repository run
40 |
41 | > npm run compile
42 |
43 | __Note:__ `npm test` will run the compilation automatically before the tests.
44 |
45 | ### Tests
46 |
47 | [Mocha](http://mochajs.org/) tests are located in the `test/` folder and can be run using the `npm run mocha` or `npm test` (with ESLint and code coverage) command.
48 |
49 | ### Documentation
50 |
51 | Feathers documentation is contained in Markdown files in the [feathers-docs](https://github.com/feathersjs/feathers-docs) repository. To change the documentation submit a pull request to that repo, referencing any other PR if applicable, and the docs will be updated with the next release.
52 |
53 | ## External Modules
54 |
55 | If you're written something awesome for Feathers, the Feathers ecosystem, or using Feathers please add it to the [showcase](https://docs.feathersjs.com/why/showcase.html). You also might want to check out the [Plugin Generator](https://github.com/feathersjs/generator-feathers-plugin) that can be used to scaffold plugins to be Feathers compliant from the start.
56 |
57 | If you think it would be a good core module then please contact one of the Feathers core team members in [Slack](http://slack.feathersjs.com) and we can discuss whether it belongs in core and how to get it there. :beers:
58 |
59 | ## Contributor Code of Conduct
60 |
61 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
62 |
63 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
64 |
65 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
66 |
67 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
68 |
69 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
70 |
71 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
72 |
--------------------------------------------------------------------------------
/test/index.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable handle-callback-err */
2 |
3 | import assert from 'assert';
4 | import request from 'request';
5 | import feathers from 'feathers';
6 | import bodyParser from 'body-parser';
7 | import {
8 | Service as todoService, verify
9 | } from 'feathers-commons/lib/test-fixture';
10 | import rest from '../src';
11 |
12 | describe('REST provider', function () {
13 | describe('CRUD', function () {
14 | let server, app;
15 |
16 | before(function () {
17 | app = feathers().configure(rest(rest.formatter))
18 | .use(bodyParser.json())
19 | .use('codes', {
20 | get (id, params, callback) {
21 | callback();
22 | },
23 |
24 | create (data, params, callback) {
25 | callback(null, data);
26 | }
27 | })
28 | .use('todo', todoService);
29 |
30 | server = app.listen(4777, () => app.use('tasks', todoService));
31 | });
32 |
33 | after(done => server.close(done));
34 |
35 | describe('Services', () => {
36 | it('sets the hook object in res.hook', done => {
37 | app.use('/hook', {
38 | get (id, params, callback) {
39 | callback(null, { description: `You have to do ${id}` });
40 | }
41 | }, function (req, res, next) {
42 | res.data.hook = res.hook;
43 | next();
44 | });
45 |
46 | request('http://localhost:4777/hook/dishes', (error, response, body) => {
47 | const hook = JSON.parse(body).hook;
48 | assert.deepEqual(hook, {
49 | id: 'dishes',
50 | params: {
51 | query: {},
52 | provider: 'rest'
53 | },
54 | method: 'get',
55 | type: 'after'
56 | });
57 | done();
58 | });
59 | });
60 |
61 | it('sets the hook object in res.hook on error', done => {
62 | app.use('/hook-error', {
63 | get () {
64 | return Promise.reject(new Error('I blew up'));
65 | }
66 | }, function (error, req, res, next) {
67 | res.status(500);
68 | res.json({
69 | hook: res.hook,
70 | error
71 | });
72 | });
73 |
74 | request('http://localhost:4777/hook-error/dishes', (error, response, body) => {
75 | const hook = JSON.parse(body).hook;
76 | assert.deepEqual(hook, {
77 | id: 'dishes',
78 | params: {
79 | query: {},
80 | provider: 'rest'
81 | },
82 | method: 'get',
83 | type: 'error'
84 | });
85 | done();
86 | });
87 | });
88 |
89 | it('GET .find', done => {
90 | request('http://localhost:4777/todo', (error, response, body) => {
91 | assert.ok(response.statusCode === 200, 'Got OK status code');
92 | verify.find(JSON.parse(body));
93 | done(error);
94 | });
95 | });
96 |
97 | it('GET .get', done => {
98 | request('http://localhost:4777/todo/dishes', (error, response, body) => {
99 | assert.ok(response.statusCode === 200, 'Got OK status code');
100 | verify.get('dishes', JSON.parse(body));
101 | done(error);
102 | });
103 | });
104 |
105 | it('POST .create', done => {
106 | let original = {
107 | description: 'POST .create'
108 | };
109 |
110 | request({
111 | url: 'http://localhost:4777/todo',
112 | method: 'post',
113 | body: JSON.stringify(original),
114 | headers: {
115 | 'Content-Type': 'application/json'
116 | }
117 | }, (error, response, body) => {
118 | assert.ok(response.statusCode === 201, 'Got CREATED status code');
119 | verify.create(original, JSON.parse(body));
120 |
121 | done(error);
122 | });
123 | });
124 |
125 | it('PUT .update', done => {
126 | let original = {
127 | description: 'PUT .update'
128 | };
129 |
130 | request({
131 | url: 'http://localhost:4777/todo/544',
132 | method: 'put',
133 | body: JSON.stringify(original),
134 | headers: {
135 | 'Content-Type': 'application/json'
136 | }
137 | }, (error, response, body) => {
138 | assert.ok(response.statusCode === 200, 'Got OK status code');
139 | verify.update(544, original, JSON.parse(body));
140 |
141 | done(error);
142 | });
143 | });
144 |
145 | it('PUT .update many', done => {
146 | let original = {
147 | description: 'PUT .update',
148 | many: true
149 | };
150 |
151 | request({
152 | url: 'http://localhost:4777/todo',
153 | method: 'put',
154 | body: JSON.stringify(original),
155 | headers: {
156 | 'Content-Type': 'application/json'
157 | }
158 | }, (error, response, body) => {
159 | let data = JSON.parse(body);
160 | assert.ok(response.statusCode === 200, 'Got OK status code');
161 | verify.update(null, original, data);
162 |
163 | done(error);
164 | });
165 | });
166 |
167 | it('PATCH .patch', done => {
168 | let original = {
169 | description: 'PATCH .patch'
170 | };
171 |
172 | request({
173 | url: 'http://localhost:4777/todo/544',
174 | method: 'patch',
175 | body: JSON.stringify(original),
176 | headers: {
177 | 'Content-Type': 'application/json'
178 | }
179 | }, (error, response, body) => {
180 | assert.ok(response.statusCode === 200, 'Got OK status code');
181 | verify.patch(544, original, JSON.parse(body));
182 |
183 | done(error);
184 | });
185 | });
186 |
187 | it('PATCH .patch many', done => {
188 | let original = {
189 | description: 'PATCH .patch',
190 | many: true
191 | };
192 |
193 | request({
194 | url: 'http://localhost:4777/todo',
195 | method: 'patch',
196 | body: JSON.stringify(original),
197 | headers: {
198 | 'Content-Type': 'application/json'
199 | }
200 | }, (error, response, body) => {
201 | assert.ok(response.statusCode === 200, 'Got OK status code');
202 | verify.patch(null, original, JSON.parse(body));
203 |
204 | done(error);
205 | });
206 | });
207 |
208 | it('DELETE .remove', done => {
209 | request({
210 | url: 'http://localhost:4777/tasks/233',
211 | method: 'delete'
212 | }, function (error, response, body) {
213 | assert.ok(response.statusCode === 200, 'Got OK status code');
214 | verify.remove(233, JSON.parse(body));
215 |
216 | done(error);
217 | });
218 | });
219 |
220 | it('DELETE .remove many', done => {
221 | request({
222 | url: 'http://localhost:4777/tasks',
223 | method: 'delete'
224 | }, (error, response, body) => {
225 | assert.ok(response.statusCode === 200, 'Got OK status code');
226 | verify.remove(null, JSON.parse(body));
227 |
228 | done(error);
229 | });
230 | });
231 | });
232 |
233 | describe('Dynamic Services', () => {
234 | it('GET .find', done => {
235 | request('http://localhost:4777/tasks', (error, response, body) => {
236 | assert.ok(response.statusCode === 200, 'Got OK status code');
237 | verify.find(JSON.parse(body));
238 | done(error);
239 | });
240 | });
241 |
242 | it('GET .get', done => {
243 | request('http://localhost:4777/tasks/dishes', (error, response, body) => {
244 | assert.ok(response.statusCode === 200, 'Got OK status code');
245 | verify.get('dishes', JSON.parse(body));
246 | done(error);
247 | });
248 | });
249 |
250 | it('POST .create', done => {
251 | let original = {
252 | description: 'Dynamic POST .create'
253 | };
254 |
255 | request({
256 | url: 'http://localhost:4777/tasks',
257 | method: 'post',
258 | body: JSON.stringify(original),
259 | headers: {
260 | 'Content-Type': 'application/json'
261 | }
262 | }, (error, response, body) => {
263 | assert.ok(response.statusCode === 201, 'Got CREATED status code');
264 | verify.create(original, JSON.parse(body));
265 |
266 | done(error);
267 | });
268 | });
269 |
270 | it('PUT .update', done => {
271 | let original = {
272 | description: 'Dynamic PUT .update'
273 | };
274 |
275 | request({
276 | url: 'http://localhost:4777/tasks/544',
277 | method: 'put',
278 | body: JSON.stringify(original),
279 | headers: {
280 | 'Content-Type': 'application/json'
281 | }
282 | }, (error, response, body) => {
283 | assert.ok(response.statusCode === 200, 'Got OK status code');
284 | verify.update(544, original, JSON.parse(body));
285 |
286 | done(error);
287 | });
288 | });
289 |
290 | it('PATCH .patch', done => {
291 | let original = {
292 | description: 'Dynamic PATCH .patch'
293 | };
294 |
295 | request({
296 | url: 'http://localhost:4777/tasks/544',
297 | method: 'patch',
298 | body: JSON.stringify(original),
299 | headers: {
300 | 'Content-Type': 'application/json'
301 | }
302 | }, (error, response, body) => {
303 | assert.ok(response.statusCode === 200, 'Got OK status code');
304 | verify.patch(544, original, JSON.parse(body));
305 |
306 | done(error);
307 | });
308 | });
309 |
310 | it('DELETE .remove', done => {
311 | request({
312 | url: 'http://localhost:4777/tasks/233',
313 | method: 'delete'
314 | }, (error, response, body) => {
315 | assert.ok(response.statusCode === 200, 'Got OK status code');
316 | verify.remove(233, JSON.parse(body));
317 |
318 | done(error);
319 | });
320 | });
321 | });
322 | });
323 |
324 | describe('HTTP status codes', () => {
325 | let app;
326 | let server;
327 |
328 | before(function () {
329 | app = feathers().configure(rest(rest.formatter))
330 | .use('todo', {
331 | get (id) {
332 | return Promise.resolve({ description: `You have to do ${id}` });
333 | },
334 |
335 | patch () {
336 | return Promise.reject(new Error('Not implemented'));
337 | },
338 |
339 | find () {
340 | return Promise.resolve(null);
341 | }
342 | });
343 |
344 | app.use(function (req, res, next) {
345 | if (typeof res.data !== 'undefined') {
346 | next(new Error('Should never get here'));
347 | } else {
348 | next();
349 | }
350 | });
351 |
352 | // Error handler
353 | app.use(function (error, req, res, next) {
354 | if (res.statusCode < 400) {
355 | res.status(500);
356 | }
357 |
358 | res.json({ message: error.message });
359 | });
360 |
361 | server = app.listen(4780);
362 | });
363 |
364 | after(done => server.close(done));
365 |
366 | it('throws a 405 for undefined service methods and sets Allow header (#99)', done => {
367 | request('http://localhost:4780/todo/dishes', (error, response, body) => {
368 | assert.ok(response.statusCode === 200, 'Got OK status code for .get');
369 | assert.deepEqual(JSON.parse(body), { description: 'You have to do dishes' }, 'Got expected object');
370 | request({
371 | method: 'post',
372 | url: 'http://localhost:4780/todo'
373 | }, (error, response, body) => {
374 | assert.equal(response.headers.allow, 'GET,PATCH');
375 | assert.ok(response.statusCode === 405, 'Got 405 for .create');
376 | assert.deepEqual(JSON.parse(body), { message: 'Method `create` is not supported by this endpoint.' }, 'Error serialized as expected');
377 | done();
378 | });
379 | });
380 | });
381 |
382 | it('throws a 404 for undefined route', done => {
383 | request('http://localhost:4780/todo/foo/bar', (error, response) => {
384 | assert.ok(response.statusCode === 404, 'Got Not Found code');
385 |
386 | done(error);
387 | });
388 | });
389 |
390 | it('empty response sets 204 status codes, does not run other middleware (#391)', done => {
391 | request('http://localhost:4780/todo', (error, response) => {
392 | assert.ok(response.statusCode === 204, 'Got empty status code');
393 |
394 | done(error);
395 | });
396 | });
397 | });
398 |
399 | it('sets service parameters and provider type', done => {
400 | let service = {
401 | get (id, params, callback) {
402 | callback(null, params);
403 | }
404 | };
405 |
406 | let server = feathers().configure(rest(rest.formatter))
407 | .use(function (req, res, next) {
408 | assert.ok(req.feathers, 'Feathers object initialized');
409 | req.feathers.test = 'Happy';
410 | next();
411 | })
412 | .use('service', service)
413 | .listen(4778);
414 |
415 | request('http://localhost:4778/service/bla?some=param&another=thing',
416 | (error, response, body) => {
417 | let expected = {
418 | test: 'Happy',
419 | provider: 'rest',
420 | query: {
421 | some: 'param',
422 | another: 'thing'
423 | }
424 | };
425 |
426 | assert.ok(response.statusCode === 200, 'Got OK status code');
427 | assert.deepEqual(JSON.parse(body), expected, 'Got params object back');
428 | server.close(done);
429 | });
430 | });
431 |
432 | it('lets you set the handler manually', done => {
433 | let app = feathers();
434 |
435 | app.configure(rest(function (req, res) {
436 | res.format({
437 | 'text/plain': function () {
438 | res.end(`The todo is: ${res.data.description}`);
439 | }
440 | });
441 | }))
442 | .use('/todo', {
443 | get (id, params, callback) {
444 | callback(null, { description: `You have to do ${id}` });
445 | }
446 | });
447 |
448 | let server = app.listen(4776);
449 | request('http://localhost:4776/todo/dishes', (error, response, body) => {
450 | assert.equal(body, 'The todo is: You have to do dishes');
451 | server.close(done);
452 | });
453 | });
454 |
455 | it('Lets you configure your own middleware before the handler (#40)', done => {
456 | let data = { description: 'Do dishes!', id: 'dishes' };
457 | let app = feathers();
458 |
459 | app.use(function defaultContentTypeMiddleware (req, res, next) {
460 | req.headers['content-type'] = req.headers['content-type'] || 'application/json';
461 | next();
462 | })
463 | .configure(rest(rest.formatter))
464 | .use(bodyParser.json())
465 | .use('/todo', {
466 | create (data, params, callback) {
467 | callback(null, data);
468 | }
469 | });
470 |
471 | let server = app.listen(4775);
472 | request({
473 | method: 'POST',
474 | url: 'http://localhost:4775/todo',
475 | body: JSON.stringify(data)
476 | }, (error, response, body) => {
477 | assert.deepEqual(JSON.parse(body), data);
478 | server.close(done);
479 | });
480 | });
481 |
482 | it('Extend params with route params and allows id property (#76, #407)', done => {
483 | const todoService = {
484 | get (id, params) {
485 | return Promise.resolve({
486 | id,
487 | appId: params.appId,
488 | paramsId: params.id
489 | });
490 | }
491 | };
492 |
493 | const app = feathers()
494 | .configure(rest())
495 | .use('/:appId/:id/todo', todoService);
496 |
497 | const expected = {
498 | id: 'dishes',
499 | appId: 'theApp',
500 | paramsId: 'myId'
501 | };
502 |
503 | const server = app.listen(6880).on('listening', function () {
504 | request('http://localhost:6880/theApp/myId/todo/' + expected.id, (error, response, body) => {
505 | assert.ok(response.statusCode === 200, 'Got OK status code');
506 | assert.deepEqual(expected, JSON.parse(body));
507 | server.close(done);
508 | });
509 | });
510 | });
511 |
512 | if (process.version > 'v6.0.0') {
513 | it('throws an error with Feathers >= v3', () => {
514 | const feathers3 = require('@feathersjs/feathers');
515 |
516 | try {
517 | feathers3().configure(rest());
518 | assert.ok(false, 'Should never get here');
519 | } catch (e) {
520 | assert.equal(e.message, `feathers-rest is not compatible with Feathers v${feathers3.version}. Use the Express framework bindings and REST adapter at @feathersjs/express instead.`);
521 | }
522 | });
523 | }
524 | });
525 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## [v1.8.1](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.8.1) (2017-10-31)
4 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v2.0.0-pre.1...v1.8.1)
5 |
6 | **Closed issues:**
7 |
8 | - Angular http implementation error [\#136](https://github.com/feathersjs-ecosystem/feathers-rest/issues/136)
9 | - Prepare for feathers v3 [\#133](https://github.com/feathersjs-ecosystem/feathers-rest/issues/133)
10 | - An in-range update of feathers is breaking the build 🚨 [\#129](https://github.com/feathersjs-ecosystem/feathers-rest/issues/129)
11 |
12 | **Merged pull requests:**
13 |
14 | - Add a warning when using with Feathers v3 [\#137](https://github.com/feathersjs-ecosystem/feathers-rest/pull/137) ([daffl](https://github.com/daffl))
15 | - Update axios to the latest version 🚀 [\#135](https://github.com/feathersjs-ecosystem/feathers-rest/pull/135) ([greenkeeper[bot]](https://github.com/apps/greenkeeper))
16 | - adding codeclimate eslint to test [\#134](https://github.com/feathersjs-ecosystem/feathers-rest/pull/134) ([ekryski](https://github.com/ekryski))
17 | - Update mocha to the latest version 🚀 [\#132](https://github.com/feathersjs-ecosystem/feathers-rest/pull/132) ([greenkeeper[bot]](https://github.com/apps/greenkeeper))
18 | - Always include route parameter [\#131](https://github.com/feathersjs-ecosystem/feathers-rest/pull/131) ([daffl](https://github.com/daffl))
19 | - Remove babel-polyfill [\#130](https://github.com/feathersjs-ecosystem/feathers-rest/pull/130) ([daffl](https://github.com/daffl))
20 | - Update debug to the latest version 🚀 [\#128](https://github.com/feathersjs-ecosystem/feathers-rest/pull/128) ([greenkeeper[bot]](https://github.com/apps/greenkeeper))
21 |
22 | ## [v2.0.0-pre.1](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v2.0.0-pre.1) (2017-07-23)
23 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.8.0...v2.0.0-pre.1)
24 |
25 | **Closed issues:**
26 |
27 | - How to return a pdf download file instead of data ? [\#125](https://github.com/feathersjs-ecosystem/feathers-rest/issues/125)
28 | - Map route parameters to `params.route` [\#124](https://github.com/feathersjs-ecosystem/feathers-rest/issues/124)
29 |
30 | **Merged pull requests:**
31 |
32 | - Map route parameters to params.route [\#126](https://github.com/feathersjs-ecosystem/feathers-rest/pull/126) ([daffl](https://github.com/daffl))
33 | - Update REST provider to Feathers v3 [\#123](https://github.com/feathersjs-ecosystem/feathers-rest/pull/123) ([daffl](https://github.com/daffl))
34 | - Update plugin infrastructure and remove client functionality [\#122](https://github.com/feathersjs-ecosystem/feathers-rest/pull/122) ([daffl](https://github.com/daffl))
35 |
36 | ## [v1.8.0](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.8.0) (2017-07-04)
37 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.7.4...v1.8.0)
38 |
39 | **Closed issues:**
40 |
41 | - Consider how to handle file upload [\#102](https://github.com/feathersjs-ecosystem/feathers-rest/issues/102)
42 |
43 | **Merged pull requests:**
44 |
45 | - add support for @angular/http [\#120](https://github.com/feathersjs-ecosystem/feathers-rest/pull/120) ([j2L4e](https://github.com/j2L4e))
46 |
47 | ## [v1.7.4](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.7.4) (2017-06-27)
48 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.7.3...v1.7.4)
49 |
50 | **Closed issues:**
51 |
52 | - Property 'axios' does not exist on type 'Transport' [\#116](https://github.com/feathersjs-ecosystem/feathers-rest/issues/116)
53 |
54 | **Merged pull requests:**
55 |
56 | - Add missing return-type annotation [\#119](https://github.com/feathersjs-ecosystem/feathers-rest/pull/119) ([couac](https://github.com/couac))
57 |
58 | ## [v1.7.3](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.7.3) (2017-06-07)
59 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.7.2...v1.7.3)
60 |
61 | **Closed issues:**
62 |
63 | - $gte query example? [\#118](https://github.com/feathersjs-ecosystem/feathers-rest/issues/118)
64 | - JSON-API + feathers-rest [\#115](https://github.com/feathersjs-ecosystem/feathers-rest/issues/115)
65 | - $in: \[\] returning unexpected results [\#113](https://github.com/feathersjs-ecosystem/feathers-rest/issues/113)
66 | - Setting 'content-type' header properly with boundary [\#110](https://github.com/feathersjs-ecosystem/feathers-rest/issues/110)
67 |
68 | **Merged pull requests:**
69 |
70 | - Add typing support for axios [\#117](https://github.com/feathersjs-ecosystem/feathers-rest/pull/117) ([elixiao](https://github.com/elixiao))
71 | - Update semistandard to the latest version 🚀 [\#111](https://github.com/feathersjs-ecosystem/feathers-rest/pull/111) ([greenkeeper[bot]](https://github.com/apps/greenkeeper))
72 | - explicitly updating qs dependency due to security vulnerability [\#109](https://github.com/feathersjs-ecosystem/feathers-rest/pull/109) ([ekryski](https://github.com/ekryski))
73 | - Update dependencies to enable Greenkeeper 🌴 [\#108](https://github.com/feathersjs-ecosystem/feathers-rest/pull/108) ([greenkeeper[bot]](https://github.com/apps/greenkeeper))
74 |
75 | ## [v1.7.2](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.7.2) (2017-04-11)
76 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.7.1...v1.7.2)
77 |
78 | **Closed issues:**
79 |
80 | - res.hook should exist in middleware after errors [\#106](https://github.com/feathersjs-ecosystem/feathers-rest/issues/106)
81 | - Add headers and remote IP [\#105](https://github.com/feathersjs-ecosystem/feathers-rest/issues/105)
82 |
83 | **Merged pull requests:**
84 |
85 | - Set res.hook on errors as well [\#107](https://github.com/feathersjs-ecosystem/feathers-rest/pull/107) ([daffl](https://github.com/daffl))
86 | - Update axios to version 0.16.0 🚀 [\#104](https://github.com/feathersjs-ecosystem/feathers-rest/pull/104) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot))
87 |
88 | ## [v1.7.1](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.7.1) (2017-03-02)
89 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.7.0...v1.7.1)
90 |
91 | **Merged pull requests:**
92 |
93 | - Improve typedefs [\#103](https://github.com/feathersjs-ecosystem/feathers-rest/pull/103) ([myknbani](https://github.com/myknbani))
94 |
95 | ## [v1.7.0](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.7.0) (2017-03-01)
96 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.6.0...v1.7.0)
97 |
98 | **Closed issues:**
99 |
100 | - Custom HTTP status code response [\#99](https://github.com/feathersjs-ecosystem/feathers-rest/issues/99)
101 |
102 | **Merged pull requests:**
103 |
104 | - Typescript Definitions [\#101](https://github.com/feathersjs-ecosystem/feathers-rest/pull/101) ([AbraaoAlves](https://github.com/AbraaoAlves))
105 | - Adding Axios to readme [\#100](https://github.com/feathersjs-ecosystem/feathers-rest/pull/100) ([corymsmith](https://github.com/corymsmith))
106 |
107 | ## [v1.6.0](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.6.0) (2016-12-31)
108 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.5.3...v1.6.0)
109 |
110 | **Closed issues:**
111 |
112 | - support axios? [\#94](https://github.com/feathersjs-ecosystem/feathers-rest/issues/94)
113 |
114 | **Merged pull requests:**
115 |
116 | - Add support for Axios as the client [\#98](https://github.com/feathersjs-ecosystem/feathers-rest/pull/98) ([daffl](https://github.com/daffl))
117 |
118 | ## [v1.5.3](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.5.3) (2016-12-30)
119 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.5.2...v1.5.3)
120 |
121 | **Closed issues:**
122 |
123 | - Client: Support Service Middleware / Generic Service [\#91](https://github.com/feathersjs-ecosystem/feathers-rest/issues/91)
124 | - fn.call is not a function\(…\) on client side with webpack [\#89](https://github.com/feathersjs-ecosystem/feathers-rest/issues/89)
125 | - $in operator doesn't work correctly through rest [\#88](https://github.com/feathersjs-ecosystem/feathers-rest/issues/88)
126 | - add angular2 http client support [\#64](https://github.com/feathersjs-ecosystem/feathers-rest/issues/64)
127 |
128 | **Merged pull requests:**
129 |
130 | - Test and fix for undefined id on get [\#97](https://github.com/feathersjs-ecosystem/feathers-rest/pull/97) ([daffl](https://github.com/daffl))
131 | - Create .codeclimate.yml [\#92](https://github.com/feathersjs-ecosystem/feathers-rest/pull/92) ([larkinscott](https://github.com/larkinscott))
132 | - Tests for nested arrays and Mocha 3 updates [\#90](https://github.com/feathersjs-ecosystem/feathers-rest/pull/90) ([daffl](https://github.com/daffl))
133 | - Update superagent to version 3.0.0 🚀 [\#87](https://github.com/feathersjs-ecosystem/feathers-rest/pull/87) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot))
134 | - Update feathers-memory to version 1.0.0 🚀 [\#86](https://github.com/feathersjs-ecosystem/feathers-rest/pull/86) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot))
135 | - Update feathers-commons to version 0.8.0 🚀 [\#85](https://github.com/feathersjs-ecosystem/feathers-rest/pull/85) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot))
136 |
137 | ## [v1.5.2](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.5.2) (2016-11-08)
138 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.5.1...v1.5.2)
139 |
140 | **Closed issues:**
141 |
142 | - Client fails to parse json response on 204 status using fetch [\#83](https://github.com/feathersjs-ecosystem/feathers-rest/issues/83)
143 |
144 | **Merged pull requests:**
145 |
146 | - Return null for no-content responses with fetch [\#84](https://github.com/feathersjs-ecosystem/feathers-rest/pull/84) ([bedeoverend](https://github.com/bedeoverend))
147 | - Swapping rm to rifraf and using relative path to \_mocha for windows support [\#82](https://github.com/feathersjs-ecosystem/feathers-rest/pull/82) ([corymsmith](https://github.com/corymsmith))
148 | - jshint —\> semistandard [\#81](https://github.com/feathersjs-ecosystem/feathers-rest/pull/81) ([corymsmith](https://github.com/corymsmith))
149 |
150 | ## [v1.5.1](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.5.1) (2016-10-21)
151 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.5.0...v1.5.1)
152 |
153 | **Closed issues:**
154 |
155 | - Rest clients should not delete/patch/update everything if `id` is undefined [\#79](https://github.com/feathersjs-ecosystem/feathers-rest/issues/79)
156 | - Distinct [\#76](https://github.com/feathersjs-ecosystem/feathers-rest/issues/76)
157 | - Sequelize Params not passed through when using REST Client [\#75](https://github.com/feathersjs-ecosystem/feathers-rest/issues/75)
158 |
159 | **Merged pull requests:**
160 |
161 | - Throw error when update, patch or remove id is undefined [\#80](https://github.com/feathersjs-ecosystem/feathers-rest/pull/80) ([daffl](https://github.com/daffl))
162 | - adding code coverage [\#78](https://github.com/feathersjs-ecosystem/feathers-rest/pull/78) ([ekryski](https://github.com/ekryski))
163 | - Rename internal route id property [\#74](https://github.com/feathersjs-ecosystem/feathers-rest/pull/74) ([daffl](https://github.com/daffl))
164 |
165 | ## [v1.5.0](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.5.0) (2016-09-09)
166 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.4.4...v1.5.0)
167 |
168 | **Merged pull requests:**
169 |
170 | - Update feathers-memory to version 0.8.0 🚀 [\#73](https://github.com/feathersjs-ecosystem/feathers-rest/pull/73) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot))
171 | - Allow using default fetch headers for all requests [\#72](https://github.com/feathersjs-ecosystem/feathers-rest/pull/72) ([bahmutov](https://github.com/bahmutov))
172 |
173 | ## [v1.4.4](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.4.4) (2016-08-18)
174 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.4.3...v1.4.4)
175 |
176 | **Merged pull requests:**
177 |
178 | - Call `next` properly in formatter [\#67](https://github.com/feathersjs-ecosystem/feathers-rest/pull/67) ([daffl](https://github.com/daffl))
179 | - Update mocha to version 3.0.0 🚀 [\#65](https://github.com/feathersjs-ecosystem/feathers-rest/pull/65) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot))
180 |
181 | ## [v1.4.3](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.4.3) (2016-07-14)
182 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.4.2...v1.4.3)
183 |
184 | **Closed issues:**
185 |
186 | - Implementation of fetch doesn't work in browser? [\#62](https://github.com/feathersjs-ecosystem/feathers-rest/issues/62)
187 | - Setting hook.method should direct the request to the chosen method. [\#60](https://github.com/feathersjs-ecosystem/feathers-rest/issues/60)
188 | - Error in REST handler: `Invalid atomic update value for $\_\_original\_remove. Expected an object, received function` [\#59](https://github.com/feathersjs-ecosystem/feathers-rest/issues/59)
189 | - Return `error.response.json\(\)` doesn't resolve using fetch [\#56](https://github.com/feathersjs-ecosystem/feathers-rest/issues/56)
190 |
191 | **Merged pull requests:**
192 |
193 | - Use response.json\(\) for converting errors [\#63](https://github.com/feathersjs-ecosystem/feathers-rest/pull/63) ([daffl](https://github.com/daffl))
194 |
195 | ## [v1.4.2](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.4.2) (2016-06-04)
196 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.4.1...v1.4.2)
197 |
198 | **Closed issues:**
199 |
200 | - Support native browser fetch [\#54](https://github.com/feathersjs-ecosystem/feathers-rest/issues/54)
201 |
202 | **Merged pull requests:**
203 |
204 | - Fix fetch reference so that it works in the browser [\#55](https://github.com/feathersjs-ecosystem/feathers-rest/pull/55) ([daffl](https://github.com/daffl))
205 |
206 | ## [v1.4.1](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.4.1) (2016-05-30)
207 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.4.0...v1.4.1)
208 |
209 | ## [v1.4.0](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.4.0) (2016-05-30)
210 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.3.0...v1.4.0)
211 |
212 | **Closed issues:**
213 |
214 | - Clients should convert error objects to feathers-errors [\#50](https://github.com/feathersjs-ecosystem/feathers-rest/issues/50)
215 |
216 | **Merged pull requests:**
217 |
218 | - Convert to Feathers errors [\#53](https://github.com/feathersjs-ecosystem/feathers-rest/pull/53) ([daffl](https://github.com/daffl))
219 | - Update superagent to version 2.0.0 🚀 [\#52](https://github.com/feathersjs-ecosystem/feathers-rest/pull/52) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot))
220 | - mocha@2.5.0 breaks build 🚨 [\#48](https://github.com/feathersjs-ecosystem/feathers-rest/pull/48) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot))
221 | - Update babel-plugin-add-module-exports to version 0.2.0 🚀 [\#45](https://github.com/feathersjs-ecosystem/feathers-rest/pull/45) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot))
222 |
223 | ## [v1.3.0](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.3.0) (2016-04-28)
224 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.2.5...v1.3.0)
225 |
226 | **Closed issues:**
227 |
228 | - Support patch, remove and update many [\#40](https://github.com/feathersjs-ecosystem/feathers-rest/issues/40)
229 | - Fix link in README for providers docs [\#38](https://github.com/feathersjs-ecosystem/feathers-rest/issues/38)
230 | - feathers-rest returns Internal Server Error on invalid login [\#36](https://github.com/feathersjs-ecosystem/feathers-rest/issues/36)
231 | - Set Allow response headers [\#21](https://github.com/feathersjs-ecosystem/feathers-rest/issues/21)
232 |
233 | **Merged pull requests:**
234 |
235 | - Add Allow HTTP headers [\#42](https://github.com/feathersjs-ecosystem/feathers-rest/pull/42) ([daffl](https://github.com/daffl))
236 | - Support patch, update and remove many in the client [\#41](https://github.com/feathersjs-ecosystem/feathers-rest/pull/41) ([daffl](https://github.com/daffl))
237 |
238 | ## [v1.2.5](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.2.5) (2016-04-13)
239 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.2.4...v1.2.5)
240 |
241 | **Fixed bugs:**
242 |
243 | - superagent doesn't set JSON Accept header [\#29](https://github.com/feathersjs-ecosystem/feathers-rest/issues/29)
244 |
245 | **Closed issues:**
246 |
247 | - Set Accept header as default [\#28](https://github.com/feathersjs-ecosystem/feathers-rest/issues/28)
248 | - Remove JSDOM as devDependency [\#25](https://github.com/feathersjs-ecosystem/feathers-rest/issues/25)
249 | - Add the ability for services to be internal only. [\#23](https://github.com/feathersjs-ecosystem/feathers-rest/issues/23)
250 |
251 | **Merged pull requests:**
252 |
253 | - Test and fix that makes sure that all cliens are accepting JSON [\#35](https://github.com/feathersjs-ecosystem/feathers-rest/pull/35) ([daffl](https://github.com/daffl))
254 | - steal-compatibility [\#33](https://github.com/feathersjs-ecosystem/feathers-rest/pull/33) ([marshallswain](https://github.com/marshallswain))
255 | - Update feathers-memory to version 0.7.0 🚀 [\#32](https://github.com/feathersjs-ecosystem/feathers-rest/pull/32) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot))
256 | - Readme: Use native 'window.fetch' [\#30](https://github.com/feathersjs-ecosystem/feathers-rest/pull/30) ([queckezz](https://github.com/queckezz))
257 |
258 | ## [v1.2.4](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.2.4) (2016-03-13)
259 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.2.3...v1.2.4)
260 |
261 | **Merged pull requests:**
262 |
263 | - Test and fix for request to serialize errors properly [\#22](https://github.com/feathersjs-ecosystem/feathers-rest/pull/22) ([daffl](https://github.com/daffl))
264 |
265 | ## [v1.2.3](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.2.3) (2016-02-24)
266 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.2.2...v1.2.3)
267 |
268 | **Merged pull requests:**
269 |
270 | - Update feathers-errors to version 2.0.1 🚀 [\#20](https://github.com/feathersjs-ecosystem/feathers-rest/pull/20) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot))
271 | - Update jsdom to version 8.0.4 🚀 [\#18](https://github.com/feathersjs-ecosystem/feathers-rest/pull/18) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot))
272 |
273 | ## [v1.2.2](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.2.2) (2016-02-11)
274 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.2.1...v1.2.2)
275 |
276 | **Merged pull requests:**
277 |
278 | - Allow to instantiate a client instance [\#16](https://github.com/feathersjs-ecosystem/feathers-rest/pull/16) ([daffl](https://github.com/daffl))
279 |
280 | ## [v1.2.1](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.2.1) (2016-02-09)
281 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.2.0...v1.2.1)
282 |
283 | ## [v1.2.0](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.2.0) (2016-02-09)
284 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.1.1...v1.2.0)
285 |
286 | **Closed issues:**
287 |
288 | - Support setting headers [\#12](https://github.com/feathersjs-ecosystem/feathers-rest/issues/12)
289 | - Errors aren't returned correctly when using fetch [\#4](https://github.com/feathersjs-ecosystem/feathers-rest/issues/4)
290 |
291 | **Merged pull requests:**
292 |
293 | - Adds support for custom headers via params [\#14](https://github.com/feathersjs-ecosystem/feathers-rest/pull/14) ([ekryski](https://github.com/ekryski))
294 | - Update feathers-memory to version 0.6.0 🚀 [\#10](https://github.com/feathersjs-ecosystem/feathers-rest/pull/10) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot))
295 | - Adding nsp check [\#8](https://github.com/feathersjs-ecosystem/feathers-rest/pull/8) ([marshallswain](https://github.com/marshallswain))
296 | - Update feathers-commons to version 0.6.0 🚀 [\#6](https://github.com/feathersjs-ecosystem/feathers-rest/pull/6) ([greenkeeperio-bot](https://github.com/greenkeeperio-bot))
297 | - Correctly handling status codes with fetch [\#5](https://github.com/feathersjs-ecosystem/feathers-rest/pull/5) ([corymsmith](https://github.com/corymsmith))
298 |
299 | ## [v1.1.1](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.1.1) (2016-01-16)
300 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.1.0...v1.1.1)
301 |
302 | **Merged pull requests:**
303 |
304 | - Fixing .npmignore entries [\#3](https://github.com/feathersjs-ecosystem/feathers-rest/pull/3) ([corymsmith](https://github.com/corymsmith))
305 |
306 | ## [v1.1.0](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.1.0) (2016-01-10)
307 | [Full Changelog](https://github.com/feathersjs-ecosystem/feathers-rest/compare/v1.0.0...v1.1.0)
308 |
309 | **Merged pull requests:**
310 |
311 | - Adding REST Feathers clients for Fetch, jQuery, Request and Superagent [\#1](https://github.com/feathersjs-ecosystem/feathers-rest/pull/1) ([daffl](https://github.com/daffl))
312 |
313 | ## [v1.0.0](https://github.com/feathersjs-ecosystem/feathers-rest/tree/v1.0.0) (2016-01-03)
314 |
315 |
316 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
--------------------------------------------------------------------------------