Giter VIP home page Giter VIP logo

resourcejs's Introduction

Resource.js - A simple Express library to reflect Mongoose models to a REST interface with a splash of Swagger.io love.

NPM version NPM download Build Status Coverage Status

Resource.js is designed to be a minimalistic Express library that reflects a Mongoose model to a RESTful interface. It does this through a very simple and extensible interface.

Installation

You can install Resource.js using NPM.

npm install --save resourcejs

Usage

Provided the following code

var express = require('express');
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var Resource = require('resourcejs');

mongoose.connect('mongodb://localhost/myapp');

// Create the app.
var app = express();

// Use the body parser.
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

// Create the schema.
var ResourceSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true
  },
  description: {
    type: String
  },
  count: {
    type: Number
  }
});

// Create the model.
var ResourceModel = mongoose.model('Resource', ResourceSchema);

// Create the REST resource.
Resource(app, '', 'resource', ResourceModel).rest();

The following rest interface would then be exposed.

  • /resource - (GET) - List all resources.
  • /resource - (POST) - Create a new resource.
  • /resource/:id - (GET) - Get a specific resource.
  • /resource/:id - (PUT) - Replaces an existing resource.
  • /resource/:id - (PATCH) - Updates an existing resource.
  • /resource/:id - (DELETE) - Deletes an existing resource.

Parameters

The Resource object takes 4 arguments.

Resource(app, route, name, model)
  • app - This is the Express application.
  • route - This is the route to "mount" this resource onto. For example, if you were doing nested resources, this could be '/parent/:parentId'
  • name - The name of the resource, which will then be used for the URL path of that resource.
  • model - The Mongoose Model for this interface.

Only exposing certain methods

You can also expose only a certain amount of methods, by instead of using the rest method, you can use the specific methods and then chain them together like so.

// Do not expose DELETE.
Resource(app, '', 'resource', ResourceModel).get().put().post().index();

Adding Before and After handlers

This library allows you to handle middleware either before or after the request is made to the Mongoose query mechanism. This allows you to either alter the query being made or, provide authentication.

For example, if you wish to provide basic authentication to every endpoint, you can use the before callback attached to the rest method like so.

npm install basic-auth-connect
var basicAuth = require('basic-auth-connect');

...
...

Resource(app, '', 'resource', ResourceModel).rest({
  before: basicAuth('username', 'password')
});

You can also target individual methods so if you wanted to protect POST, PUT, and DELETE but not GET and INDEX you would do the following.

Resource(app, '', 'resource', ResourceModel).rest({
  beforePut: basicAuth('username', 'password'),
  beforePost: basicAuth('username', 'password'),
  beforeDelete: basicAuth('username', 'password')
});

You can also do this by specifying the handlers within the specific method calls like so.

Resource(app, '', 'resource', ResourceModel)
  .get()
  .put({
    before: basicAuth('username', 'password'),
    after: function(req, res, next) {
      console.log("PUT was just called!");
    }
  })
  .post({
  	before: basicAuth('username', 'password')
  });

After Handlers: The after handlers allow you to modify the contents of the resource before it is handed over to the client. It does this by setting a resource object on the res object. This resource object follows the following schema.

  • status: The status code that will be sent to the client.
  • error: Any error that may have been caused within the request.
  • item: The resource item that is going to be sent to the client.

For example, if you have a resource that has a title that is sent to the user, you could change that title by doing the following.

Resource(app, '', 'resource', ResourceModel).get({
  after: function(req, res, next) {
    res.resource.item.title = 'I am changing!!';
    next();
  }
});

Virtual Resources

Virtual resources are not represented by mongodb documents. Instead they are generated by functions acting on existing mongodb documents, typically via the mongodb aggregate pipeline.

Resource.js supports this feature by passing options to the resource.virtual method. The virtual method expects at least the path and the before option params to be set:

  • path : Set to the name of the virtual resource. This will be used in the generated url.
  • before: Set to a function that will be used to generate the virtual resource.

This will result in a generated REST end-point with the following pattern:

  • /[resource-name]/virtual/[virtual-resource-name]

For example, defining a virtual resource called avg-weight for a resource called elephant will give a url of:

  • /elephant/virtual/avg-weight

The shape of json data returned is determined by a before function. This function will act on an existing document to return a virtual resource of arbitrary shape. Typically a mongodb aggregate function will be used here although any valid model query is in fact allowed.

For example, to set up two virtual resources, max-price and max-stock, for a resource called product you would write code similar to the following:

var express = require('express');
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var resource = require('resourcejs');

// Create the app.
var app = express();

// Use the body parser.
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

//Create the Schema
var productSchema = new Schema({
  name: String,
  price: Number,
  stock: Number
});

//Create the model
var productModel = mongoose.model('product', productSchema);

You can then define a couple of aggregate functions called max-price and max-stock using the mongoose model.

//Define the virtual resource aggregate functions
var maxPrice = function(req, res, next) {
  req.modelQuery = productModel.aggregate().group({
    _id: null,
    maxPrice: {
      $max: '$price'
    }
  });
  return next();
};

var maxStock = function(req, res, next) {
  req.modelQuery = productModel.aggregate().group({
    _id: null,
    maxStock: {
      $max: '$stock'
    }
  }).select;
  return next();
};

You can then setup the product via resource.js by passing in the path and the before function for each virtual resource, like this:

//Create the virtual Product resources
resource(app, '', 'product', productModel)
  .virtual({
    path: 'max-price',
    before: maxPrice
  })
  .virtual({
    path: 'max-stock',
    before: maxStock
  });

Finally you can retrieve the virtual resources using their generated urls:

#####max-price

  • /product/virtual/max-price

returns the max-price virtual resource as json:

{
  _id:null,
  maxPrice:123
}

#####max-stock

  • /product/virtual/max-stock

returns the max-stock virtual resource as json:

{
  _id:null,
  maxStock:321
}

Calling the PATCH method

ResourceJS fully implements the JSON-Patch spec RFC-6902. This allows for partial updates to be made directly to a resource and is therefore a very efficient way of updating a resource.

With JSON-Patch you can also test whether a resource is suitable for a updating and if it is then only update the fields you actually need to update. You can apply an arbitrary sequence of tests and actions (see the spec RFC-6902 for more details) and if any one should fail all the changes are rolled back and the resource is left untouched.

For example, using the Resource schema above, we will increment just the numeric count field but only if the count value is the same as the value we are currently holding, in other words - only update the value if nobody else has updated it in the meantime.

This example uses the request npm package

request = require('request')

function increaseCount(currentCount, resourceId, next) {
  var options, patch;
  patch = [
    {
      "op": "test",
      "path": "/count",
      "value": currentCount
    }, {
      "op": "replace",
      "path": "/count",
      "value": currentCount + 1
    }
  ];
  options = {
    method: 'PATCH',
    uri: "/resource/" + resourceId,
    body: patch,
    json: true
  };
  return request(options, function(err, response, data) {
    return next(data);
  });
}
});

Adding custom queries

Using the method above, it is possible to provide some custom queries in your before middleware. We can do this by adding a modelQuery to the req object during the middleware. This query uses the Mongoose query mechanism that you can see here http://mongoosejs.com/docs/api.html#query_Query-where.

For example, if we wish to show an index that filters ages greater than 18, we would do the following.

Resource(app, '', 'user', UserModel).rest({
  before: function(req, res, next) {
    req.modelQuery = this.model.where('age').gt(18);
  }
});

Passing write options on PUT, POST, PATCH, and DELETE requests

It is possible to pass a set of options to the underlying Document.save() and Document.remove() commands. This can be useful when plugins expect data to be passed in as options. We can do this by adding a writeOptions object to the req object during middleware. This uses the Mongoose mechanism that you can see here https://mongoosejs.com/docs/api.html#document_Document-save.

For example, a set of options can be added by doing the following.

Resource(app, '', 'user', UserModel).rest({
  before: function(req, res, next) {
    req.writeOptions = { actingUserId: req.user.id };
  }
});

Nested Resources

With this library, it is also pretty easy to nest resources. Here is an example of how to do it.

var express = require('express');
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var Resource = require('../Resource');

// Create the app.
var app = express();

// Use the body parser.
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

// Parent model
var Parent = mongoose.model('Parent', new mongoose.Schema({
  name: {
    type: String,
    required: true
  }
}));

// Child model.
var Child = mongoose.model('Child', new mongoose.Schema({
  name: {
    type: String,
    required: true
  },
  parent: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Parent',
    index: true,
    required: true
  }
}));

// The parent REST interface.
Resource(app, '', 'parent', Parent).rest();

// The child REST interface.
Resource(app, '/parent/:parentId', 'child', Child).rest({

  // Add a before handler to include filter and parent information.
  before: function(req, res, next) {
    req.body.parent = req.params.parentId;
    req.modelQuery = this.model.where('parent', req.params.parentId);
    next();
  }
});

This would now expose the following...

  • /parent - (GET) - List all parents.
  • /parent - (POST) - Create a new parent.
  • /parent/:parentId - (GET) - Get a specific parent.
  • /parent/:parentId - (PUT) - Updates an existing parent.
  • /parent/:parentId - (DELETE) - Deletes an existing parent.
  • /parent/:parentId/child - (GET) - List all children of a parent.
  • /parent/:parentId/child - (POST) - Create a new child.
  • /parent/:parentId/child/:childId - (GET) - Get a specific child per parent.
  • /parent/:parentId/child/:childId - (PUT) - Update a child for a parent.
  • /parent/:parentId/child/:childId - (DELETE) - Delete a child for a parent.

Filtering the results.

The index() that is created is capable of doing some complex filtering using Query arguments within the URL. They are described as the following.

Filter Query Example Description
equal equals /users?gender=male both return all male users
not equal ne /users?gender__ne=male returns all users who are not male (female and x)
greater than gt /users?age__gt=18 returns all users older than 18
greater than or equal to gte /users?age__gte=18 returns all users 18 and older (age should be a number property)
less than lt /users?age__lt=30 returns all users age 29 and younger
less than or equal to lte /users?age__lte=30 returns all users age 30 and younger
in in /users?gender__in=female,male returns all female and male users
nin nin /users?age__nin=18,21 returns all users who are not 18 or 21
exists=true exists /users?age__exists=true returns all users where the age is provided.
exists=false exists /users?age__exists=false returns all users where the age is not provided.
Regex regex /users?username__regex=/^travis/i returns all users with a username starting with travis
limit limit /users?limit=5 limits results to the specified amount
skip skip /users?skip=10 skip to the specified record in the result set
select select /users?select=first_name,last_name return only the specified fields

Adding Swagger.io v2 documentation

Along with auto-generating API's for your application, this library also is able to auto generate Swagger.io documentation so that your API's are well documented and can be easily used and understood by everyone.

Each Resource object has the ability to generate the Swagger docs for that resource, and this can then be combined to create the Swagger docs necessary to feed into the Swagger UI tools.

Getting the swagger documentation for a resource

var resource = Resource(app, '', 'resource', ResourceModel).rest();

// Print out the Swagger docs for this resource.
console.log(resource.swagger());

You can then use this to create a full specification for you API with all your resources by doing the following.

var _ = require('lodash');

// Define all our resources.
var resources = {
	user: Resource(app, '', 'user', UserModel).rest(),
	group: Resource(app, '', 'group', GroupModel).rest(),
	role: Resource(app, '', 'role', RoleModel).rest()
};

// Get the Swagger paths and definitions for each resource.
var paths = {};
var definitions = {};
_.each(resources, function(resource) {
  var swagger = resource.swagger();
  paths = _.assign(paths, swagger.paths);
  definitions = _.assign(definitions, swagger.definitions);
});

// Define the specification.
var specification = {
  swagger: '2.0',
  info: {
    description: '',
    version: '0.0.1',
    title: '',
    contact: {
      name: '[email protected]'
    },
    license: {
      name: 'MIT',
      url: 'http://opensource.org/licenses/MIT'
    }
  },
  host: 'localhost:3000',
  basePath: '',
  schemes: ['http'],
  definitions: definitions,
  paths: paths
};

// Show the specification at the URL.
app.get('/spec', function(req, res, next) {
	res.json(specification);
});

resourcejs's People

Contributors

andrewperry avatar biofractal avatar brendanbond avatar claytongulick avatar coderofsalvation avatar dependabot[bot] avatar makinde avatar michaeljcole avatar mikekotikov avatar randallknutson avatar robblovell avatar roflankisel avatar ryanformio avatar sebajs avatar sefriol avatar smokingcat avatar travist avatar yuryrybak avatar yuryrybaksoftensity avatar zackurben avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

resourcejs's Issues

Allow complex population

Right now population is limited to a list of fields, e.g. friends parent. Mongoose supports more sophisticated population parameters. In order to support those, the library would simple need to be open to parsing the populate query param as JSON, and then passing that to the underlying query. A relatively small change, and I'm happy to take a stab at it as well. (Right now I'm actually manually parsing the populate param in a before handler to get this functionality.

Do I need to add am issue when I generate a pull request

Is it good form to write up the reasoning behind a pull request as an issue and then link off to the pull request?

I have a couple of pull requests pending and I was wondering if creating a corresponding issue for each would better help the documentation chain.

Add ability to pass options to Document.save()

There are Mongoose plugins that require that options be passed into queries and save commands. The current req.modelQuery allows this to be done for read requests (index and get). For POST, PUT, and PATCH requests, it currently is not possible.

My recommendation is the before hooks are allowed to set req.saveOptions and that req.saveOptions be passed into all calls to Document.save().

I can whip up a diff for this as well.

Add documentation for `hooks`

before/after handlers are well documented, but it takes a while of digging through the code to understand how hooks are subtly different.

Bug / Question: Virtual Resources

While reading through the code and trying to make an implementation using ResourceJS, I noticed that when you define virtual Resource, there is no way to define a path for a resource or when you do it's always defined as "virtual/undefined".

https://github.com/travist/resourcejs/blob/master/Resource.js#L615

As far I have understood, here ResourceJS tries to refer into path variable in options. However it's always overwritten by new object and path is never assigned to that object. Is this a bug or am I missing something here?

https://github.com/travist/resourcejs/blob/master/Resource.js#L182

Lists of object and "populate"

"Populate" doesn't work with lists of objects.

This test will fail:

it('Should not populate paths that are not a reference', function(done) {
    request(app)
      .get('/test/resource1?name=noage&populate=list')
      .end(function(err, res) {
        if (err) {
          return done(err);
        }

        var response = res.body;

        // Check statusCode
        assert.equal(res.statusCode, 200);

        // Check main resource
        assert.equal(response[0].title, 'No Age');
        assert.equal(response[0].description, 'No age');
        assert.equal(response[0].name, 'noage');
        assert.equal(response[0].list.length, 1);

        // Check populated resource
        assert.equal(response[0].list[0].label, '1');
        assert.equal(response[0].list[0].data.length, 1);
        assert.equal(response[0].list[0].data[0], refDoc1Response._id);
        done();
      });
  });

can't get example in README.md to work

$ npm install resourcejs express mongoose body-parser

And with this app.js:

var express = require('express');
var bodyParser = require('body-parser');
var mongoose = require('mongoose');
var Resource = require('resourcejs');

// Create the app.
var app = express();

// Use the body parser.
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());

// Create the schema.
var ResourceSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true
  },
  description: {
    type: String
  },
  count: {
    type: Number
  }
});

// Create the model.
var ResourceModel = mongoose.model('Resource', ResourceSchema);

// Create the REST resource.
Resource(app, '', 'resource', ResourceModel).rest();

app.listen(3000)

It runs, but when i do GET /Resource nothing happens..it hangs in the browsers, and in curl too:

$ curl -v -H 'Content-Type: application/json' http://localhost:3000/Resource
* Hostname was NOT found in DNS cache
*   Trying ::1...
* Connected to localhost (::1) port 3000 (#0)
> GET /Resource HTTP/1.1
> User-Agent: curl/7.38.0
> Host: localhost:3000
> Accept: */*
> Content-Type: application/json
> 
^C

Installed modules:

accepts@(unsaved) -> 1.3.3
array-flatten@(unsaved) -> 1.1.1
async@(unsaved) -> 2.1.4
bluebird@(unsaved) -> 2.10.2
bson@(unsaved) -> 1.0.4
buffer-shims@(unsaved) -> 1.0.0
bytes@(unsaved) -> 2.4.0
composable-middleware@(unsaved) -> 0.3.0
content-disposition@(unsaved) -> 0.5.1
content-type@(unsaved) -> 1.0.2
cookie@(unsaved) -> 0.3.1
core-util-is@(unsaved) -> 1.0.2
cookie-signature@(unsaved) -> 1.0.6
debug@(unsaved) -> 2.2.0
depd@(unsaved) -> 1.1.0
ee-first@(unsaved) -> 1.1.1
destroy@(unsaved) -> 1.0.4
encodeurl@(unsaved) -> 1.0.1
es6-promise@(unsaved) -> 3.2.1
escape-html@(unsaved) -> 1.0.3
etag@(unsaved) -> 1.7.0
fast-json-patch@(unsaved) -> 0.5.7
finalhandler@(unsaved) -> 0.5.0
fresh@(unsaved) -> 0.3.0
forwarded@(unsaved) -> 0.1.0
hooks-fixed@(unsaved) -> 1.2.0
http-errors@(unsaved) -> 1.5.1
iconv-lite@(unsaved) -> 0.4.13
inherits@(unsaved) -> 2.0.3
ipaddr.js@(unsaved) -> 1.1.1
isarray@(unsaved) -> 1.0.0
kareem@(unsaved) -> 1.2.1
lodash@(unsaved) -> 4.17.4
media-typer@(unsaved) -> 0.3.0
merge-descriptors@(unsaved) -> 1.0.1
methods@(unsaved) -> 1.1.2
mime@(unsaved) -> 1.3.4
mime-db@(unsaved) -> 1.25.0
mime-types@(unsaved) -> 2.1.13
mongodb@(unsaved) -> 2.2.22
mongodb-core@(unsaved) -> 2.1.7
mpath@(unsaved) -> 0.2.1
mpromise@(unsaved) -> 0.5.5
mquery@(unsaved) -> 2.2.3
ms@(unsaved) -> 0.7.1
muri@(unsaved) -> 1.2.1
negotiator@(unsaved) -> 0.6.1
node-paginate-anything@(unsaved) -> 1.0.0
on-finished@(unsaved) -> 2.3.0
parseurl@(unsaved) -> 1.3.1
path-to-regexp@(unsaved) -> 0.1.7
process-nextick-args@(unsaved) -> 1.0.7
proxy-addr@(unsaved) -> 1.1.2
qs@(unsaved) -> 6.2.0
range-parser@(unsaved) -> 1.2.0
raw-body@(unsaved) -> 2.1.7
readable-stream@(unsaved) -> 2.1.5
regexp-clone@(unsaved) -> 0.0.1
require_optional@(unsaved) -> 1.0.0
resolve-from@(unsaved) -> 2.0.0
semver@(unsaved) -> 5.3.0
send@(unsaved) -> 0.14.1
serve-static@(unsaved) -> 1.11.1
setprototypeof@(unsaved) -> 1.0.2
sliced@(unsaved) -> 1.0.1
statuses@(unsaved) -> 1.3.1
string_decoder@(unsaved) -> 0.10.31
type-is@(unsaved) -> 1.6.14
unpipe@(unsaved) -> 1.0.0
util-deprecate@(unsaved) -> 1.0.2
utils-merge@(unsaved) -> 1.0.0
vary@(unsaved) -> 1.1.0

Should `before` and `before[method]` handlers be merged?

Right now if I have a method set as a before handler, then I add a beforePost handler, the former will be replaced by the latter on POST requests. This was a little surprising to me. I assumed they would be additive.

I'm currently using the framework where I have base options that are applied to every resource. Each resource can then add its own handlers. So my base options includes a before handler that modifies every request, globally. Then in the options for a specific resource, I was to add a new handler that does specific additional login on just a POST request. To my surprise, adding a beforePost replaced the global beforeHandler.

Would you be open to having it so before and beforeMethod handlers are always concatenated together? I'm happy to make a PR, but wanted to check before I got too far.

Cannot read property 'hooks' of undefined with only index

If I mount only index method, then it throws this exception:

/Users/******/app/server/node_modules/resourcejs/Resource.js:415
          options.hooks.index.before.call(
                 ^

TypeError: Cannot read property 'hooks' of undefined
    at Object.<anonymous> (/Users/carloscasalar/nodes/rt-ship-generator/app/server/node_modules/resourcejs/Resource.js:415:18)
    at /Users/carloscasalar/nodes/rt-ship-generator/app/server/node_modules/kareem/index.js:264:21
    at /Users/carloscasalar/nodes/rt-ship-generator/app/server/node_modules/kareem/index.js:127:16
    at _combinedTickCallback (internal/process/next_tick.js:67:7)
    at process._tickCallback (internal/process/next_tick.js:98:9)

My sample controller:

'use strict';
var resource = require('resourcejs');
/**
 * Controller for MountpointType services
 * @module controllers/mount-point-type.server.controller
 */
module.exports = mountpointTypeController;
function mountpointTypeController(app, basepath, resourceName){
  console.log('Mounting path %s/%s', basepath, resourceName);
  resource(app, basepath, resourceName, app.models.MountpointType).index();
}

embedded objects in a schema are not appearing in the swagger Model Schema

In both of the ways of specifying an embedded object below, the field in the parent of the embedded object does not appear in the Model Schema:

Schema = require('mongoose').Schema
case1 = new Schema(
  {
    field1: String
    field2: String
  }
)
case2 = {
  field3: String
  field4: String
}
root = {
  case1: case1
  case2: case2
}
object = new Schema(root, {strict: false})

Resulting Model Schema is missing case1 and doesn't show case2 the way I would expect.

{
  "case2.field3": "string",
  "case2.field4": "string",
  "_id": "string"
}

Expected:

{
  "case1": {"field1": "string", "field2": "string"}
  "case2": {"field3": "string", "field4": "string"}
  "_id": "string"
}

Note, that if the field is an array, it does show up correctly:

{
  "case3": [{"field5": "string", "field6": "string"}]
}

could not Fetch all records.

I want to get all records present in mongodb database. I've used

var Resource = require('resourcejs');

module.exports = function(app, route) {

// Setup the controller for REST;
Resource(app, '', route, app.models.usermodel).rest();
// Return middleware.
return function(req, res, next) {
next();
};
};

Above is the code on the server side and at client side in angularjs app I've used the below code

angular.module('clientApp')
.controller('UserCtrl', function ($scope, User, $route, logedin, $location) {
if(!logedin){
$location.url('/login');
}
$scope.route = $route;
$scope.users = User.getList().$object;
});

Everything is working fine. But I'm only able to get 10 Results at a time and I can't find a method to get all records in Resourcejs. I want to get all users at a time. Is this possible with Resourcejs or I've to migrate my code to another approach.

The regex doesn't work as well

When I create the resource with for example field path with value "someText/someText" (with slash) and I want to get all resources which DON'T have slash by regex (apiUrl/resource?path__regex=/^((?!/).)*$/g ). I get a wrong result((( I get all resources also which don't match with regex. As I understand it's a BUG.
This BUG is in this line
When you match regexes you get wrong parts because in the example regex has 3 slash
This is console.log(parts). As you see we have wrong match((
[ '/^((?!\\/).)*$', '^((?!\\', ').)*$', index: 0, input: '/^((?!\\/).)*$/g', groups: undefined ]

Document all the fields that can be set on `req` in before handlers

There are a bunch of things that can be set on req in a before handler that will affect how the endpoints behave. It'd be great if all of those were documented in the README.md. Some ones that I see by doing a quick glance.

  • req.modelQuery
  • req.model
  • req.countQuery
  • req.skipResource

Swagger doc says 201, but search returns 200

Hi, I'm using ResourceJS to build an API from Mongoose. The API has a GET {resource}?limit=10 route.

This route returns a 200 w/ data. The ResourceJS Swagger spec returns only 201. What's up with that? Seems like a bug. Is there a good reason? Would you like a PR?

Also, I'm trying to make a swagger spec and need to customize the doc. Is there a good way to do this w/o forking the project? Specifically, I need to customize the request and response body definitions. Swagger is new for me so suggestions are welcome.

Thanks!

Mike

Why does `Resource.respond()` call `next()` at the end

It seems like that method handles sending a response. By calling next, other middleware on the app continue to get run, which means a central 404 handler will eventually get called if the express app is doing other things.

Would it make sense to remove line 159 in Resource.js?

How would I integrate the mongoose 'Population' feature?

Thanks for writing resourcejs, I am having a play around and so far have enjoyed its ease of use.

I was wondering how I would make use of the mongoose Populate() feature to create post-hoc joins? I have the following simple, test model.

product = new Schema
    name    : String
    price       : Number

house = new Schema
    client      : String
    products    : [{ type: Schema.ObjectId, ref: 'product' }]

This is working just fine however, as you would expect, the house.products array is returning a list of naked objectIds whereas I would like this get this list populated with the actual product.

Looking through your examples I can see you support nested models, which is great and I suspect that I must need to use a before handler in a similar fashion but I am not sure how to go about this.

Can you show my me how I would mongoose.populate my model using resourcejs please?

Thanks

Index requests returning 206 without content range specified

Resource index requests will respond with an HTTP 206 without the user specifying the content-range (not expected and/or normal behavior). To get around this currently, I am adding ?limit=9999 to my queries, where 9999 is some number over my total number of records, thereby forcing the request to return all results and a 200 rather than 206.

Required fields appear marked as optional

I was able to fix it by un-commenting and refactoring lines 140 and 164:

definitions[modelName].required.push(name);

But I'm curious why this has been commented out in the first place?

How to add custom Error Handler?

I'd like to add a global error handler either on express or resourcejs level (to do additional logging/handling) but I don't see an options or params to do that and on top the default error handler seems to swallow the errors.

const error = (err, req, res, next) => {
      if (err) {
        res.status(400).json({
          status: 400,
          message: err.message || err,
        });
      }
      else {
        return next();
      }
    };

    routeStack = [...routeStack, error.bind(this)]

Any suggestions on how to let errors through to express middleware or have a custom error handler added to resourcejs?

Unable to use get for all data inside my react state

ERR

{message: "Request failed with status code 500", name: "Error", description: undefined, number: undefined, fileName: undefined, …}
code: undefined
columnNumber: undefined
config:
adapter: ƒ xhrAdapter(config)
data: undefined
headers: {Accept: "application/json, text/plain, */*"}
maxContentLength: -1
method: "get"
timeout: 0
transformRequest: [ƒ]
transformResponse: [ƒ]
url: "/book"
validateStatus: ƒ validateStatus(status)
xsrfCookieName: "XSRF-TOKEN"
xsrfHeaderName: "X-XSRF-TOKEN"
__proto__: Object
description: undefined
fileName: undefined
lineNumber: undefined
message: "Request failed with status code 500"
name: "Error"
number: undefined
stack: "Error: Request failed with status code 500↵    at createError (http://localhost:3000/static/js/0.chunk.js:46032:15)↵    at settle (http://localhost:3000/static/js/0.chunk.js:46253:12)↵    at XMLHttpRequest.handleLoad (http://localhost:3000/static/js/0.chunk.js:45507:7)"
__proto__: Object

server using 5000port react proxy enabled linked to 5000

postman is able to fetch but react is unable with axios...I'm getting error 500 on GET /resource requests, when resources is empty collection in MongoDB.

Missing an example on the best way to use upsert with Resource.js

I have a likes collection that should with a User reference and a Post.

Because the each post can only be liked once by a given user I need to use something like upsert and I wonder what is the best way to structure this kind of functionality when using resource.js

countQuery.find is not a function

Not sure what I'm doing wrong here...

TypeError: countQuery.find is not a function
Object.<anonymous> (/../server/node_modules/resourcejs/Resource.js:356:20)

after handlers never invoked from method processing

The before handlers work as expected but the after handlers do not. This applies to the generic after handler and the method specific after[method] handlers.

To replicate this the following options object can be used:

options:
    beforeGet:(req, res, next)->
        console.log 'before'
        next()

    afterGet:(req, res, next)->
        console.log 'after'
        next()

Calling the the GET route for the applicable resource results in the before message being logged but not the after message.

Investigations reveal that the after function declared in the options is never called, possibly due to the method processing terminating without calling next().

The following method processing does not include a call to next() and so does not call the after method

    /**
     * Register the GET method for this resource.
     */
    get: function(options) {
      this.methods.push('get');
      app.get.apply(app, this.register(this.route + '/:' + this.name + 'Id', function(req, res, next) {
        var query = req.modelQuery || this.model;
        query.findOne({"_id": req.params[this.name + 'Id']}, function(err, item) {
          if (err) return this.respond(res, 500, err);
          if (!item) return this.respond(res, 404);
          res.json(item);
        }.bind(this));
      }, options));
      return this;
    },

The same code plus a call to next() now successfully calls the after function:

    /**
     * Register the GET method for this resource.
     */
    get: function(options) {
      this.methods.push('get');
      app.get.apply(app, this.register(this.route + '/:' + this.name + 'Id', function(req, res, next) {
        var query = req.modelQuery || this.model;
        query.findOne({"_id": req.params[this.name + 'Id']}, function(err, item) {
          if (err) return this.respond(res, 500, err);
          if (!item) return this.respond(res, 404);
          res.json(item);
          next() // <-- calling next to invoke the after handler
        }.bind(this));
      }, options));
      return this;
    },

I am not sure if calling next() here is the appropriate solution however it does highlight the problem.

`get()` passes `search` to before hook

https://github.com/travist/resourcejs/blob/master/Resource.js#L559

When the before hook is called, it passes search as the item instead of query. Why is that? It makes it harder to write hooks because index() passes the query as item. It'd be great to be consistent and have them both pass query. That seems like the way to go since you can still access search via the query.

The only problem is that this is a breaking change. Folks could be depending on search being passed instead of query. Might be something to flag for the next major version bump?

I'm currently working around this by accessing the query through req.modelQuery. It works, but is a strange dependency to have between my before handlers and before hooks.

Middleware Upgrade: Usage & Discussion

@travist
@zackurben

I have been experimenting with the new middleware upgrade and I thought it might be interesting for you to see how I am using it. It is working beautifully and definitely solves the problem I was having previously where I was calling my own API via http - so that is really good news. However I have come across a potential issue that I will explain further down.

Working Example

Here is a working test example (coffeescript) of how I am currently using the middleware. I am using the async package to chain together a series of asynchronous calls:

router = require('express').Router()
async = require 'async'

router.get '/test/:key', (req, res, next) ->
    resj = req.app.resourcejs
    async.waterfall [
        (cb)->
            req.query.key = req.params.key
            resj['/api'].get.call this, req, res, ->
                return cb res.resource.status unless res.resource.status is 200
                cb null, res.resource.item[0]
        (api, cb)->
            req.body =
                key : api.key
                vid : '123'
                mac : '123'
            resj['/verification'].post.call this, req, res, ->
                return cb res.resource.status unless res.resource.status is 201
                cb null, res.resource.item
    ], (err, verification)->
        console.log 'err', err
        console.log 'verification', verification
        next()

module.exports = router

The process I am replicating here is

  1. get an api object with a given key.
    Note: the key value is a url-part and not a query param but the resourcejs uri expects a query param thusly: /api?key=[value] so I just copy the key value from req.params to req.query and it works as expected.
  2. post a new verification object based on details from the retrieved api.
    Note: I have to pack the req.body with the details of the new verification
  3. log some values and call next()

Problem

The problem I have is the final clause in the async chain. In the working example above I am just logging out some values and calling next()

    ], (err, verification)->
        console.log 'err', err
        console.log 'verification', verification
        next()

In fact I want to return the error status code, if one has been sent, or otherwise render a jade template - something like this:

    ], (err, verification)->
        return res.sendStatus err if err?
        res.render 'verify', title:'verify', vid:verification.vid

Doing this gives the error: Can't set headers after they are sent presumably because resourcejs has already sent the resource?

I would appreciate your thoughts on how I should proceed with this use-case.

Override index() query parameters filtering behavior

Index() currently automatically applies filters based on the query parameter that is passed in the request.

Is there a way to override the behavior for a particular query parameter? Say I want to query item?availability=1, instead of filtering items based on availability field on the item object, I want to do something else.

I tried to do the following in the after handler:

if (req.query.availability == 1) {
  console.log("Test Test");
  next();
}

It does print the message in the console, but also returns 500 as follows:

{
  "status": 500,
  "message": "Cast to ObjectId failed for value \"1\" at path \"availability\"",
  "errors": {}
}

Thanks!

Can nested resources be created from a schema and its subdocument?

When looking at the example code for the Nested Resources section, PARENT and CHILD refer to two Mongoose objects that handle two separate MongoDB collections. I confirmed this when running the code, as an empty Child collection was created in my database.

However, would there be a way to nest resources, using instead a document + subdocument like the following?

parent {
  parentId: xxx,
  parentName: "yyy",

  child: [
    {
    childId: zzz,
    childName: "aaa"
    },
  ],

}

With something along these lines defining their relationship in Mongoose:

var ChildSchema = new mongoose.Schema({
    // variables + id
});

var ParentSchema = new mongoose.Schema({
  // parent variables, id
  child: [ChildSchema]
});

In other words, I'd like to know if the parent and child REST interfaces can be defined with Resource() so that they refer to the main document and its nested contents.

Thanks for reading!

Within an after function, how can I discover the actual route used?

I am attempting to add the ability to mock out calls to resource.js. When a call is made to a resource.js end-point, the actual call is swapped out for an equivalent call to a mock url (apiary blueprint in my case).

I am doing this in the following way:

before:->
    (req, res, next) ->
        req.skipResource = true;
        next()

after:(baseUrl)->
    (req, res, next) ->
        resourceUrl = res.resource.route //<- Problem is Here
        options =
            method: "#{req.method}"
            uri:"#{baseUrl}/#{resourceUrl}"

        request options, req.headers, (err, response, data)->
            res.json data

The before function prevents resource.js from processing the call. The after function composes the mock-url and calls it returning any results. The mock-url consists of two parts, the baseUrl (the static part of the url pointing to the mock api) and the resourceUrl, the actual route that resource.js would have used if it had been allowed to process.

The problem I have is getting hold of the resourceUrl component. The code above sees me trying the res.resource.route but this is undefined. I think this would be wrong in any case as I really need the final route resource.js would be using, including any ids, filters etc.

Do you have any idea if this route is available currently? If not, some pointers to the best place to store this route for each call so that it is available to the after function would be greatly appreciated.

Thanks

Dependencies out of date?

It shows This is an UNSTABLE release of Mongoose.

##############################################################
#
#   !!! MONGOOSE WARNING !!!
#
#   This is an UNSTABLE release of Mongoose.
#   Unstable releases are available for preview/testing only.
#   DO NOT run this in production.
#
##############################################################

Can we add support for virtual resources?

Resourcejs does not currently support the concept of a REST 'virtual resource', that is a resource that is not represented by mongodb document and therefore is not defined by a mongoose schema.

Given the original aims of resourcejs to provide CRUD access to mongodb domain documents, this is a perfectly reasonable constraint, however it seems to me to be a vital omission if resourcejs is to be used (as I am using it) as a REST API generator that services and extends a mongodb domain. For example I have used resourcejs to enhance mongodb with field level encryption and sliding expiration TTL fields.

Therefore, do you agree that adding the ability to generate endpoints for schema-less, virtual resources is a worthwhile enhancement and if so do you have any ideas how we might go about implementing this? I am happy to do the work but I would greatly appreciate your design input.

Thanks

?limit and ?skip does not work?

I followed the documentation to set server side code. Everything else works fine, but for some reason ?limit and ?skip in url does not work.

i am getting:
{
"status": 500,
"message": "Failed to parse: { find: "cities", filter: {}, limit: "5" }. 'limit' field must be numeric.",
"errors": {}
}

any suggestions?
here is all the dependency versions:

    "body-parser": "^1.15.0",
    "express": "^4.13.4",
    "lodash": "^4.5.0",
    "method-override": "^2.3.5",
    "mongoose": "^4.4.6",
    "resourcejs": "^1.0.6"

Here is my server side code

var express = require('express');
var mongoose = require('mongoose');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
var Resource = require('resourcejs');
var _ = require('lodash');

// Create the application.
var app = express();

// Add Middleware necessary for REST API's
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
app.use(methodOverride('X-HTTP-Method-Override'));

app.use(function(req, res, next) {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
  res.header('Access-Control-Allow-Headers', 'Content-Type');
  next();
});

// Connect to MongoDB
mongoose.connect('mongodb://localhost/hereseas_dev');
mongoose.connection.once('open', function() {
    // Load the models.
    app.models = require('./models/index');
    // Load the routes.
    var routes = require('./routes');

   Resource(app, '', 'resource', app.models.city).get().index();

  console.log('Listening on port 3000...');
  app.listen(3000); //set up http server, listen to port 3000
});

Type definitions

Has anyone created, or considered creating a type definition file for resourcejs? @types/resourcejs

Exposing aggregate query results via a resourcejs api

I have defined a series of domain resources (via mongoose schemas) and exposed them via resourcejs. Lovely.

Now I need to perform an series of aggregate queries that consume those resources and return composite results that have no concrete representation in the database. I would like these results to be exposed via my resourcejs REST interface.

I see no theoretical difficulty in doing this RESTfully, these aggregate results are just resources that I can specify via a unique url, however I am struggling to see how I can fit this into the resourcejs way of doing things.

Perhaps I need to specify an series of named aggregate result schemas and restrict them to be accessible only via the GET method. I could then specify a before (or is that after) function that performs the appropriate aggregation query and sets the returned item?

I would be grateful for any pointers or advice you might have.

Thanks

Allow passing of options down to `save()` method

I'm using some mongoose plugins that require me to pass options when doing queries and saves. Thanks being able to set req.modelQuery in before handlers, I can add my options for read requests. For the writes, I need to be able to pass options to save (in PATCH, POST, and PUT requests).

Ideal would be to be able to set something like req.saveOptions in a before handler.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.