{"id":13989707,"url":"https://github.com/travist/resourcejs","last_synced_at":"2025-05-16T01:07:54.665Z","repository":{"id":26254083,"uuid":"29701236","full_name":"travist/resourcejs","owner":"travist","description":"An minimalistic Express.js library that will reflect a Mongoose model onto a RESTful interface with a splash of Swagger.io love.","archived":false,"fork":false,"pushed_at":"2025-02-26T21:55:05.000Z","size":1306,"stargazers_count":115,"open_issues_count":32,"forks_count":31,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-05-04T04:31:56.396Z","etag":null,"topics":["express","expressjs","javascript","mongoose","nodejs","swagger"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/travist.png","metadata":{"files":{"readme":"README.md","changelog":"Changelog.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2015-01-22T21:29:05.000Z","updated_at":"2025-04-28T07:43:47.000Z","dependencies_parsed_at":"2023-02-12T03:01:18.534Z","dependency_job_id":"ba780c5a-a0e0-48ae-a438-5c7b01f5ccd2","html_url":"https://github.com/travist/resourcejs","commit_stats":{"total_commits":355,"total_committers":28,"mean_commits":"12.678571428571429","dds":0.704225352112676,"last_synced_commit":"6181a4b40251a9ccfca9f1de62046102345cc7ae"},"previous_names":[],"tags_count":81,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travist%2Fresourcejs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travist%2Fresourcejs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travist%2Fresourcejs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travist%2Fresourcejs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/travist","download_url":"https://codeload.github.com/travist/resourcejs/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254261943,"owners_count":22041304,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["express","expressjs","javascript","mongoose","nodejs","swagger"],"created_at":"2024-08-09T13:01:59.049Z","updated_at":"2025-05-16T01:07:49.650Z","avatar_url":"https://github.com/travist.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"Resource.js - A simple Express library to reflect Mongoose models to a REST interface with a splash of Swagger.io love.\n==============================================================\n[![NPM version][npm-image]][npm-url]\n[![NPM download][download-image]][download-url]\n[![Build Status](https://travis-ci.org/travist/resourcejs.svg?branch=master)](https://travis-ci.org/travist/resourcejs)\n[![Coverage Status](https://coveralls.io/repos/github/Sefriol/resourcejs/badge.svg?branch=master)](https://coveralls.io/github/Sefriol/resourcejs?branch=master)\n\n[npm-image]: https://img.shields.io/npm/v/resourcejs.svg?style=flat-square\n[npm-url]: https://npmjs.org/package/resourcejs\n[download-image]: https://img.shields.io/npm/dm/resourcejs.svg?style=flat-square\n[download-url]: https://npmjs.org/package/resourcejs\n\nResource.js is designed to be a minimalistic Express library that reflects a Mongoose\nmodel to a RESTful interface. It does this through a very simple and extensible interface.\n\nInstallation\n--------------\nYou can install Resource.js using NPM.\n\n```\nnpm install --save resourcejs\n```\n\nUsage\n--------------\nProvided the following code\n\n```javascript\nvar express = require('express');\nvar bodyParser = require('body-parser');\nvar mongoose = require('mongoose');\nvar Resource = require('resourcejs');\n\nmongoose.connect('mongodb://localhost/myapp');\n\n// Create the app.\nvar app = express();\n\n// Use the body parser.\napp.use(bodyParser.urlencoded({extended: true}));\napp.use(bodyParser.json());\n\n// Create the schema.\nvar ResourceSchema = new mongoose.Schema({\n  title: {\n    type: String,\n    required: true\n  },\n  description: {\n    type: String\n  },\n  count: {\n    type: Number\n  }\n});\n\n// Create the model.\nvar ResourceModel = mongoose.model('Resource', ResourceSchema);\n\n// Create the REST resource.\nResource(app, '', 'resource', ResourceModel).rest();\n```\n\nThe following rest interface would then be exposed.\n\n * ***/resource*** - (GET) - List all resources.\n * ***/resource*** - (POST) - Create a new resource.\n * ***/resource/:id*** - (GET) - Get a specific resource.\n * ***/resource/:id*** - (PUT) - Replaces an existing resource.\n * ***/resource/:id*** - (PATCH) - Updates an existing resource.\n * ***/resource/:id*** - (DELETE) - Deletes an existing resource.\n\nParameters\n----------------\nThe ```Resource``` object takes 4 arguments.\n\n```javascript\nResource(app, route, name, model)\n```\n\n - ***app*** - This is the Express application.\n - ***route*** - This is the route to \"mount\" this resource onto. For example, if you were doing nested resources, this could be '/parent/:parentId'\n - ***name*** - The name of the resource, which will then be used for the URL path of that resource.\n - ***model*** - The Mongoose Model for this interface.\n\nOnly exposing certain methods\n-------------------\nYou can also expose only a certain amount of methods, by instead of using\nthe ***rest*** method, you can use the specific methods and then chain them\ntogether like so.\n\n```javascript\n// Do not expose DELETE.\nResource(app, '', 'resource', ResourceModel).get().put().post().index();\n```\n\nAdding Before and After handlers\n-------------------\nThis library allows you to handle middleware either before or after the\nrequest is made to the Mongoose query mechanism.  This allows you to\neither alter the query being made or, provide authentication.\n\nFor example, if you wish to provide basic authentication to every endpoint,\nyou can use the ***before*** callback attached to the ***rest*** method like so.\n\n```\nnpm install basic-auth-connect\n```\n\n```javascript\nvar basicAuth = require('basic-auth-connect');\n\n...\n...\n\nResource(app, '', 'resource', ResourceModel).rest({\n  before: basicAuth('username', 'password')\n});\n```\n\nYou can also target individual methods so if you wanted to protect POST, PUT, and DELETE\nbut not GET and INDEX you would do the following.\n\n```javascript\nResource(app, '', 'resource', ResourceModel).rest({\n  beforePut: basicAuth('username', 'password'),\n  beforePost: basicAuth('username', 'password'),\n  beforeDelete: basicAuth('username', 'password')\n});\n```\n\nYou can also do this by specifying the handlers within the specific method calls like so.\n\n```javascript\nResource(app, '', 'resource', ResourceModel)\n  .get()\n  .put({\n    before: basicAuth('username', 'password'),\n    after: function(req, res, next) {\n      console.log(\"PUT was just called!\");\n    }\n  })\n  .post({\n  \tbefore: basicAuth('username', 'password')\n  });\n```\n\n***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.\n\n  - status: The status code that will be sent to the client.\n  - error: Any error that may have been caused within the request.\n  - item: The resource item that is going to be sent to the client.\n\nFor 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.\n\n```javascript\nResource(app, '', 'resource', ResourceModel).get({\n  after: function(req, res, next) {\n    res.resource.item.title = 'I am changing!!';\n    next();\n  }\n});\n```\n\nVirtual Resources\n-----------------\nVirtual resources are not represented by mongodb documents. Instead they are generated by functions acting on existing mongodb documents, typically via the mongodb `aggregate` pipeline.\n\nResource.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:\n\n* `path`  : Set to the name of the virtual resource. This will be used in the generated url.\n* `before`: Set to a function that will be used to generate the virtual resource.\n\nThis will result in a generated REST end-point with the following pattern:\n\n* /[resource-name]/virtual/[virtual-resource-name]\n\nFor example, defining a virtual resource called `avg-weight` for a resource called `elephant` will give a url of:\n\n* /elephant/virtual/avg-weight\n\nThe 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.\n\nFor 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:\n\n```javascript\nvar express = require('express');\nvar bodyParser = require('body-parser');\nvar mongoose = require('mongoose');\nvar resource = require('resourcejs');\n\n// Create the app.\nvar app = express();\n\n// Use the body parser.\napp.use(bodyParser.urlencoded({extended: true}));\napp.use(bodyParser.json());\n\n//Create the Schema\nvar productSchema = new Schema({\n  name: String,\n  price: Number,\n  stock: Number\n});\n\n//Create the model\nvar productModel = mongoose.model('product', productSchema);\n\n```\nYou can then define a couple of aggregate functions called `max-price` and `max-stock` using the mongoose model.\n\n```javascript\n//Define the virtual resource aggregate functions\nvar maxPrice = function(req, res, next) {\n  req.modelQuery = productModel.aggregate().group({\n    _id: null,\n    maxPrice: {\n      $max: '$price'\n    }\n  });\n  return next();\n};\n\nvar maxStock = function(req, res, next) {\n  req.modelQuery = productModel.aggregate().group({\n    _id: null,\n    maxStock: {\n      $max: '$stock'\n    }\n  }).select;\n  return next();\n};\n```\n\nYou can then setup the `product` via resource.js by passing in the `path` and the `before` function for each virtual resource, like this:\n\n```javascript\n//Create the virtual Product resources\nresource(app, '', 'product', productModel)\n  .virtual({\n    path: 'max-price',\n    before: maxPrice\n  })\n  .virtual({\n    path: 'max-stock',\n    before: maxStock\n  });\n```\n\nFinally you can retrieve the virtual resources using their generated urls:\n\n#####max-price\n* /product/virtual/max-price\n\nreturns the `max-price` virtual resource as json:\n```javascript\n{\n  _id:null,\n  maxPrice:123\n}\n```\n\n#####max-stock\n* /product/virtual/max-stock\n\nreturns the `max-stock` virtual resource as json:\n```javascript\n{\n  _id:null,\n  maxStock:321\n}\n```\n\nCalling the PATCH method\n----------------------\nResourceJS fully implements the JSON-Patch spec [RFC-6902](https://tools.ietf.org/html/rfc6902). This allows for partial updates to be made directly to a resource and is therefore a very efficient way of updating a resource.\n\nWith 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](https://tools.ietf.org/html/rfc6902) for more details) and if any one should fail all the changes are rolled back and the resource is left untouched.\n\nFor 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.\n\nThis example uses the [request](https://www.npmjs.com/package/request) npm package\n\n```javascript\nrequest = require('request')\n\nfunction increaseCount(currentCount, resourceId, next) {\n  var options, patch;\n  patch = [\n    {\n      \"op\": \"test\",\n      \"path\": \"/count\",\n      \"value\": currentCount\n    }, {\n      \"op\": \"replace\",\n      \"path\": \"/count\",\n      \"value\": currentCount + 1\n    }\n  ];\n  options = {\n    method: 'PATCH',\n    uri: \"/resource/\" + resourceId,\n    body: patch,\n    json: true\n  };\n  return request(options, function(err, response, data) {\n    return next(data);\n  });\n}\n});\n```\n\nAdding custom queries\n---------------------------------\nUsing the method above, it is possible to provide some custom queries in your ***before*** middleware.\nWe can do this by adding a ***modelQuery*** to the ***req*** object during the middleware. This query\nuses the Mongoose query mechanism that you can see here http://mongoosejs.com/docs/api.html#query_Query-where.\n\nFor example, if we wish to show an index that filters ages greater than 18, we would do the following.\n\n```javascript\nResource(app, '', 'user', UserModel).rest({\n  before: function(req, res, next) {\n    req.modelQuery = this.model.where('age').gt(18);\n  }\n});\n```\n\nPassing write options on PUT, POST, PATCH, and DELETE requests\n--------------------------------------------------------------\nIt 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.\n\nFor example, a set of options can be added by doing the following.\n\n```javascript\nResource(app, '', 'user', UserModel).rest({\n  before: function(req, res, next) {\n    req.writeOptions = { actingUserId: req.user.id };\n  }\n});\n```\n\nNested Resources\n-----------------\nWith this library, it is also pretty easy to nest resources. Here is an example of how to do it.\n\n```javascript\nvar express = require('express');\nvar bodyParser = require('body-parser');\nvar mongoose = require('mongoose');\nvar Resource = require('../Resource');\n\n// Create the app.\nvar app = express();\n\n// Use the body parser.\napp.use(bodyParser.urlencoded({extended: true}));\napp.use(bodyParser.json());\n\n// Parent model\nvar Parent = mongoose.model('Parent', new mongoose.Schema({\n  name: {\n    type: String,\n    required: true\n  }\n}));\n\n// Child model.\nvar Child = mongoose.model('Child', new mongoose.Schema({\n  name: {\n    type: String,\n    required: true\n  },\n  parent: {\n    type: mongoose.Schema.Types.ObjectId,\n    ref: 'Parent',\n    index: true,\n    required: true\n  }\n}));\n\n// The parent REST interface.\nResource(app, '', 'parent', Parent).rest();\n\n// The child REST interface.\nResource(app, '/parent/:parentId', 'child', Child).rest({\n\n  // Add a before handler to include filter and parent information.\n  before: function(req, res, next) {\n    req.body.parent = req.params.parentId;\n    req.modelQuery = this.model.where('parent', req.params.parentId);\n    next();\n  }\n});\n```\n\nThis would now expose the following...\n\n * ***/parent*** - (GET) - List all parents.\n * ***/parent*** - (POST) - Create a new parent.\n * ***/parent/:parentId*** - (GET) - Get a specific parent.\n * ***/parent/:parentId*** - (PUT) - Updates an existing parent.\n * ***/parent/:parentId*** - (DELETE) - Deletes an existing parent.\n * ***/parent/:parentId/child*** - (GET) - List all children of a parent.\n * ***/parent/:parentId/child*** - (POST) - Create a new child.\n * ***/parent/:parentId/child/:childId*** - (GET) - Get a specific child per parent.\n * ***/parent/:parentId/child/:childId*** - (PUT) - Update a child for a parent.\n * ***/parent/:parentId/child/:childId*** - (DELETE) - Delete a child for a parent.\n\nFiltering the results.\n--------------------------------\nThe ```index()``` that is created is capable of doing some complex filtering using Query arguments within the URL. They are described as the following.\n\n| Filter                       | Query    | Example                                              | Description                                                      |\n|------------------------------|----------|------------------------------------------------------|------------------------------------------------------------------|\n| **equal**                    | `equals` | `/users?gender=male` \t\t\t                     | both return all male users                                       |\n| **not equal**                | `ne`     | `/users?gender__ne=male`                             | returns all users who are not male (`female` and `x`)            |\n| **greater than**             | `gt`     | `/users?age__gt=18`                                  | returns all users older than 18                                  |\n| **greater than or equal to** | `gte`    | `/users?age__gte=18`                                 | returns all users 18 and older (age should be a number property) |\n| **less than**                | `lt`     | `/users?age__lt=30`                                  | returns all users age 29 and younger                             |\n| **less than or equal to**    | `lte`    | `/users?age__lte=30`                                 | returns all users age 30 and younger                             |\n| **in**                       | `in`     | `/users?gender__in=female,male`                      | returns all female and male users                                |\n| **nin**                      | `nin`    | `/users?age__nin=18,21`                              | returns all users who are not 18 or 21                           |\n| **exists=true**              | `exists` | `/users?age__exists=true`                            | returns all users where the age is provided.                     |\n| **exists=false**             | `exists` | `/users?age__exists=false`                           | returns all users where the age is not provided.                 |\n| **Regex**                    | `regex`  | `/users?username__regex=/^travis/i`                  | returns all users with a username starting with travis           |\n| **limit**                    | `limit` | `/users?limit=5`                                     | limits results to the specified amount\n| **skip**                     | `skip` | `/users?skip=10`                                     | skip to the specified record in the result set\n| **select**                   | `select` | `/users?select=first_name,last_name`               | return only the specified fields\n\nAdding Swagger.io v2 documentation\n--------------------------------\nAlong with auto-generating API's for your application, this library also is able to\nauto generate Swagger.io documentation so that your API's are well documented and can\nbe easily used and understood by everyone.\n\nEach Resource object has the ability to generate the Swagger docs for that resource,\nand this can then be combined to create the Swagger docs necessary to feed into the\nSwagger UI tools.\n\n***Getting the swagger documentation for a resource***\n```javascript\nvar resource = Resource(app, '', 'resource', ResourceModel).rest();\n\n// Print out the Swagger docs for this resource.\nconsole.log(resource.swagger());\n```\n\nYou can then use this to create a full specification for you API with all your resources\nby doing the following.\n\n```javascript\nvar _ = require('lodash');\n\n// Define all our resources.\nvar resources = {\n\tuser: Resource(app, '', 'user', UserModel).rest(),\n\tgroup: Resource(app, '', 'group', GroupModel).rest(),\n\trole: Resource(app, '', 'role', RoleModel).rest()\n};\n\n// Get the Swagger paths and definitions for each resource.\nvar paths = {};\nvar definitions = {};\n_.each(resources, function(resource) {\n  var swagger = resource.swagger();\n  paths = _.assign(paths, swagger.paths);\n  definitions = _.assign(definitions, swagger.definitions);\n});\n\n// Define the specification.\nvar specification = {\n  swagger: '2.0',\n  info: {\n    description: '',\n    version: '0.0.1',\n    title: '',\n    contact: {\n      name: 'test@example.com'\n    },\n    license: {\n      name: 'MIT',\n      url: 'http://opensource.org/licenses/MIT'\n    }\n  },\n  host: 'localhost:3000',\n  basePath: '',\n  schemes: ['http'],\n  definitions: definitions,\n  paths: paths\n};\n\n// Show the specification at the URL.\napp.get('/spec', function(req, res, next) {\n\tres.json(specification);\n});\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftravist%2Fresourcejs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftravist%2Fresourcejs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftravist%2Fresourcejs/lists"}