{"id":24663870,"url":"https://github.com/fatmatto/express-toolkit","last_synced_at":"2025-10-08T00:31:18.169Z","repository":{"id":35028478,"uuid":"183370402","full_name":"fatmatto/express-toolkit","owner":"fatmatto","description":"Auto generate REST endpoints on top of express and mongoose","archived":false,"fork":false,"pushed_at":"2025-01-16T03:06:19.000Z","size":3027,"stargazers_count":7,"open_issues_count":23,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-01-16T04:22:00.308Z","etag":null,"topics":["api","express","http","mongodb","mongoose","rest"],"latest_commit_sha":null,"homepage":"https://fatmatto.github.io/express-toolkit","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/fatmatto.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}},"created_at":"2019-04-25T06:27:43.000Z","updated_at":"2024-06-17T07:16:08.000Z","dependencies_parsed_at":"2024-09-12T20:17:40.246Z","dependency_job_id":"f4ac96b5-1a1c-4038-8954-dc6ad6c2cf92","html_url":"https://github.com/fatmatto/express-toolkit","commit_stats":{"total_commits":136,"total_committers":4,"mean_commits":34.0,"dds":0.1029411764705882,"last_synced_commit":"5a67046d98d62cc83e0ffc8cf0aac577bd474329"},"previous_names":[],"tags_count":29,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fatmatto%2Fexpress-toolkit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fatmatto%2Fexpress-toolkit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fatmatto%2Fexpress-toolkit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fatmatto%2Fexpress-toolkit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fatmatto","download_url":"https://codeload.github.com/fatmatto/express-toolkit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235665642,"owners_count":19026236,"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":["api","express","http","mongodb","mongoose","rest"],"created_at":"2025-01-26T05:17:18.272Z","updated_at":"2025-10-08T00:31:12.834Z","avatar_url":"https://github.com/fatmatto.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n\n\u003cdiv align=\"center\"\u003e\n\n# Express Toolkit\n\nTiny little utilities for reducing expressjs boilerplate code when building simple, mongodb backed, http apis\n\n\u003c/div\u003e\n\n[![Build Status](https://travis-ci.org/fatmatto/express-toolkit.svg?branch=master)](https://travis-ci.org/fatmatto/express-toolkit)\n[![Maintainability](https://api.codeclimate.com/v1/badges/40cff05fa81b87114ae2/maintainability)](https://codeclimate.com/github/fatmatto/express-toolkit/maintainability)\n[![codecov](https://codecov.io/gh/fatmatto/express-toolkit/branch/master/graph/badge.svg)](https://codecov.io/gh/fatmatto/express-toolkit)\n\n\n- [Express Toolkit](#express-toolkit)\n  - [TL;DR](#tldr)\n  - [Models, Controllers and Routers](#models-controllers-and-routers)\n  - [Default endpoints](#default-endpoints)\n  - [Disable endpoints](#disable-endpoints)\n  - [Sorting](#sorting)\n  - [Pagination](#pagination) \n  - [Projection](#projection)\n  - [Custom primary key](#custom-primary-key)\n  - [Hooks](#hooks)\n      - [List of hooks](#list-of-hooks)\n    - [Examples](#examples)\n## TL;DR\n\nWith express-toolkit you can easily create a basic REST resource and mount it into an express application. The app will provide basic CRUD methods:\n\n```javascript\nconst express = require('express')\nconst Resource = require('../../src/resource')\nconst mongoose = require('mongoose')\n\n// Let's create our Model with Mongoose\nconst schema = new mongoose.Schema({\n  name: String\n})\n\nconst PetsResource = new Resource({\n  name: 'pets',\n  id: 'uuid',\n  model: mongoose.model('pets', schema, 'pets')\n})\n\n\nPetsResource.registerHook('pre:find', (req, res, next) =\u003e {\n  console.log('Looking for Pets')\n  next()\n})\n\n// Remember to extend the router AFTER adding hooks,\n// otherwise the router will be overwritten without this route\nPetsResource.router.get('/actions/eat',(req,res) =\u003e {\n  return res.send('Om nom nom')\n})\n\n// Now the Express related stuff\nconst app = express()\nconst port = 3000\n\napp.get('/', (req, res) =\u003e res.send('Hello World!'))\n\n// Here we mount the Pets resource under the /pets path\nPetsResource.mount('/pets', app)\n\n// After mongoose is ready, we start listening on the TCP port\nmongoose.connect('mongodb://localhost:27017/pets', {})\n  .then(() =\u003e {\n    console.log('Connection to Mongodb Established')\n    app.listen(port, () =\u003e console.log(`Example app listening on port ${port}!`))\n  })\n  .catch(error =\u003e {\n    console.log('Unable to establish connection to Mongodb', error)\n  })\n\n```\n\nUnder the hood, the resource object uses three other objects: Model, Controller and Router.\n\n- Routers are plain express routers to which the library mounts some default REST routes\n- Controllers implement CRUD methods\n- Models define a mongoose model\n\nWhen you create a resource object, the library will create a model a controller and a router for you, if you need to add custom logic to those components you can retrieve them as properties of the resource:\n```javascript\n// Let's create our Model with Mongoose\nconst schema = new mongoose.Schema({\n  name: String\n})\n\nconst PetsResource = new Resource({\n  name: 'pets',\n  id: 'uuid',\n  model: mongoose.model('pets', schema, 'pets')\n})\n\n// Let's add a hook to the controller\nconst ctrl = PetsResource.controller\n\n// more on this method later in this document\nctrl.registerHook('post:create',(req,res,next) =\u003e {\n  console.log(\"Hello I created a resource\")\n  next()\n})\n\n\n// Let's add a custom route to the router\nconst router = PetsResource.router\n\nrouter.get('/hello/world',(req,res,next) =\u003e {\n  res.send(\"Hello\")\n})\n```\n\n## Models, Controllers and Routers\n\nSuppose we need to build an http microservice for handling dinosaurs (tired of cats).\n\nFirst of all we will need a model file, powered by mongoose\n```javascript\nconst mongoose = require('mongoose')\nconst Schema = mongoose.Schema\n\n// Dinosaurs are simple\nconst DinosaurSchema = new Schema({\n  name: {\n    type: String,\n    required:true\n  },\n  type: {\n    type: String,\n    required: true\n  }\n})\n\nconst DinosaurModel = mongoose.model('Dinosaur', DinosaurSchema, 'dinosaurs')\n\nmodule.exports = {DinosaurSchema, DinosaurModel}\n```\n\n\nThen the controller file\n\n```javascript\nconst { Controller } = require('express-toolkit')\nconst { DinosaurModel } = require('./path/to/dinosaur.model.js')\n\nconst myController = new Controller({\n  model: DinosaurModel,\n  name: 'dinosaurs'\n})\nmodule.exports = myController\n```\n\nFinally the router file\n\n```javascript\nconst { buildRouter } = require('express-toolkit')\nconst DinosaurController = require('./dinosaur.controller.js')\n\nmodule.exports = buildRouter({\n  controller: DinosaurController,\n  options: {} // See expressJS router options\n})\n```\n\n\nThen, somewehere in your express app, where you mount routes:\n```javascript\nconst express = require('express')\nconst app = express()\nconst dinosaursResource = require('./path/to/dinosaur.router.js')\n//...\napp.use('/dinosaurs',dinosaursResource)\n// ...\napp.listen(1337)\n```\n## Default endpoints\n\nIn the following table, every path showed in the Path column is meant to be appended to the resource base path which simply is `/\u003cresourcename\u003e`. Following the dinosaurs examples, would be `/dinosaurs`\n\n\n| Name          | Http verb | Path            | Description                                                                            |\n| ------------- | --------- | --------------- | -------------------------------------------------------------------------------------- |\n| Create        | POST      | /               | Creates a new resource and returns it.                                                 |\n| List          | GET       | /               | Get a paginated and filtered list of resources of the given type                       |\n| GetById       | GET       | /{uuid}         | Get a resource by id                                                                   |\n| UpdateById    | PUT       | /{uuid}         | Updates a resource                                                                     |\n| UpdateByQuery | PUT       | /               | Updates resources that matches query parameters                                        |\n| PatchById     | PATCH     | /{uuid}         | Updates a resource by id using PATCH semantics                                         |\n| ReplaceById   | PUT       | /{uuid}/replace | Replaces a resource by id. Primary id field (and _id if not the same) are not replaced |\n| DeleteById    | DELETE    | /{uuid}         | Deletes a resource                                                                     |\n| DeleteByQuery | DELETE    | /               | Deletes resources matching filters in the querystring                                  |\n| Count         | GET       | /               | Count resources in collection matching filters in the querysting                       |\n\n## Disable endpoints\n\nBy default, all endpoints are enabled, to control which endpoints should be disabled, you can use the `endpoints` router parameter\n\n```javascript\n// Default behaviour, endpoints is an optional parameter\nconst router = buildRouter({\n  controller: require('./mycontroller.js'),\n  endpoints: {\n    find: true,\n    findById: true,\n    create: true,\n    updateById: true,\n    updateByQuery: true,\n    deleteById: true,\n    deleteByQuery: true,\n    count: true,\n    patchById: true,\n    replaceById: true\n  }\n})\n// Default resource deletion\nconst router = buildRouter({\n  controller: require('./mycontroller.js'),\n  endpoints: {\n    deleteById: true,\n    deleteByQuery: true\n  }\n})\n```\n\n## Sorting\n\nGET endpoints support result sorting thanks to two query string parameters:\n- `sortorder \u003cString\u003e` that can only have two values: `DESC` for descending sorting and `ASC` for ascending order.\n- `sortby \u003cString\u003e` can be used to select the sorting parameter.\n\nFor example, the following request would sort dinosaurs by age, oldest to youngest:\n\n```http\nGET /dinosaurs?sortby=age\u0026sortorder=DESC\n```\n## Pagination\nGET endpoints support result pagination through `skip` and `limit` parameters:\n- `skip \u003cNumber\u003e` tells the endpoint how many results to skip\n- `limit \u003cNumber` tells the endpoint how many results to include in the response\n\nTo implement a pagination scheme, you can leverage these two parameters in the following way: Suppose you want to return `R` results per page and you want to return page number `P`, you just need to set limit to `R` and skip to `(P-1)*R`\n\n## Projection\n\nSometimes you don't need the whole resource object but just some of its attributes, in these cases you can use the `fields` query string parameter.\n\nSuppose the dinosaur resource has name, type and age attributes, but we just want names and age:\n\n```http\nGET /dinosaurs?fields=name,age\n```\n\nOr just names\n\n```http\nGET /dinosaurs?fields=name\n```\n\nOr every field but the age and the name\n\n```http\nGET /dinosaurs?fields=-age,-name\n```\n\nIf you don't specify a `fields` parameter, every attribute will be returned.\n\n## Custom primary key\n\nBy defaults, resources are handled as if their primary key is the `_id` field, which is automatically added by mongodb. Sometimes you might want to provide your own key such as an `uuid` field added to the model. For such cases you can provide the id attribute to the controller's config:\n\n```javascript\nconst myController = new Controller({\n  model: MyModel,\n  name: 'dinosaurs',\n  id: 'uuid'\n})\n```\n\n\n\n\n## Hooks\n\nEvery resource endpoint can have multiple *pre* and *post* hooks. These hooks will be run by the router before and after the related controller method.\n\nTypically, in `pre` hooks you will want to manually edit requests or do some kind of prior validation on the request, while on `post` hooks you would fetch/add more data or generate other actions such as logging business logic events.\n\n#### List of hooks\n\n- pre:count\n- post:count\n- pre:find\n- post:find\n- pre:findById\n- post:findById\n- pre:create\n- post:create\n- pre:updateById\n- post:updateById\n- pre:updateByQuery\n- post:updateByQuery\n- pre:deleteById\n- post:deleteById\n- pre:deleteByQuery\n- post:deleteByQuery\n- pre:patchById\n- post:patchById\n- pre:replaceById\n- post:replaceById\n- pre:*\n- post:*\n- pre:finalize\n\n`pre:finalize` is called on every endpoint, just before sending the response payload to the client.\nHere you can hijack `req.toSend` and update it as you need.\n\n`pre:*` if defined, is called on every endpoint of that resource before any other \"pre\" hook, in the same way `post:*` is called after any other post hook. For every endpoint the order is:\n\n- `pre:*`\n- `pre:\u003cmethodName\u003e`\n- `middleware`\n- `post:\u003cmethodname\u003e`\n- `post:*`\n- `pre:finalize`\n- `finalize`\n\n\nFor example, you might want to check the `Accept` HTTP header and convert the response from JSON to YAML, or XML.\n\n### Examples\n```javascript\nconst { Controller } = require('express-toolkit')\nconst { DinosaurModel } = require('./path/to/dinosaur.model.js')\n\nconst myController = new Controller({\n  model: DinosaurModel,\n  name: 'dinosaurs'\n})\n\n// Check authorization on all dinosaurs routes:\nmyController.registerHook('pre:*', (req,res,next) =\u003e {\n  //This is just an example, a bad auth example.\n  if (req.headers.authorization !== \"supersecret\") {\n    return res.sendStatus(401)\n  }\n  next()\n})\n\n// Force all find queries to look for velociraptor type\nmyController.registerHook('pre:find', (req,res,next) =\u003e {\n  req.query.type = 'velociraptor'\n  next()\n})\n\n// Before returning dinosaurs to the client we convert timestamps to date strings\nmyController.registerHook('post:find', (req,res,next) =\u003e {\n  req.toSend = req.toSend.map(dinosaur =\u003e {\n    let dino = Object.assign({},dinosaur)\n    dino.createdAt = String(new Date(dino.createdAt))\n    return dino\n  })\n  next()\n})\n\nmodule.exports = myController\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffatmatto%2Fexpress-toolkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffatmatto%2Fexpress-toolkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffatmatto%2Fexpress-toolkit/lists"}