{"id":20135546,"url":"https://github.com/raisely/parkes-controller","last_synced_at":"2026-06-01T01:31:06.522Z","repository":{"id":42097662,"uuid":"114422265","full_name":"raisely/parkes-controller","owner":"raisely","description":"RESTful controllers for Koa","archived":false,"fork":false,"pushed_at":"2023-01-24T01:11:55.000Z","size":334,"stargazers_count":0,"open_issues_count":12,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-12-02T10:52:27.799Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/raisely.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-12-16T00:32:03.000Z","updated_at":"2020-02-19T06:34:03.000Z","dependencies_parsed_at":"2023-02-13T07:40:32.768Z","dependency_job_id":null,"html_url":"https://github.com/raisely/parkes-controller","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/raisely/parkes-controller","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raisely%2Fparkes-controller","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raisely%2Fparkes-controller/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raisely%2Fparkes-controller/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raisely%2Fparkes-controller/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/raisely","download_url":"https://codeload.github.com/raisely/parkes-controller/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raisely%2Fparkes-controller/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33756575,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2024-11-13T21:15:31.917Z","updated_at":"2026-06-01T01:31:06.505Z","avatar_url":"https://github.com/raisely.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Parkes Controller\n\nA framework for quickly setting up **REST**ful and **CRUD** compliant APIs for sequelize models.\n\n## Dependencies\n\nParkes Controller is built for [koa 2](https://github.com/koajs/koa) and requires async/await in node 7.6\nParkes Controller interfaces to Sequelize Models.\n\n# Getting Started\n\n`npm install --save raisely/parkes-controller`\n\n```javascript\nconst parkesController = require('parkes-controller');\n\nconst User = sequelize.define('User', ...); // define the sequalize resouce\n\n// declare the controller model\nclass UserController extends ParkesController {\n\n  // define custom handlers for CRUD requests...\n  show(ctx, next) {\n    super(context);\n    console.log('Retrieved user resource: ', ctx.state.data)\n    await next();\n  }\n\n  // hook into specific CRUD events...\n  async afterIndex(ctx, collection) {\n      // do something cool during the request...\n  }\n\n  async beforeCreate(ctx, newRecord) {\n    // Validate values in newRecord\n  }\n}\n\n// init the controller (with options)\nconst userController = new UserController('user', options);\n\n// hook up to your resource router\nconst users = new restUp.resource('user', userController);\n\n// and then bind to your app\napp.use(users.routes);\n```\n\nParkes Controller works in conjunction with [ParkesRouter](https://github.com/raisely/parkes-router) and [ParkesPresenter](https://github.com/raisely/parkes-presenter) for routing requests and returning responses. Neither of these are strictly necessary, you can set up routes manually, just make sure you have some middleware that takes `ctx.state.data` and puts it on `ctx.body`.\n\n## Initializing a controller\n\nGenerally you'll want to extend the parkes controller to provide additional methods or override certain actions.\n\n```javascript\nclass MyController extends ParkesController {\n\n    // you can omit the constructor...\n    constructor(name, options){\n        super(name, options);\n        // your custom controller props\n    }\n\n    // CRUD hooks, modify REST-ful requests before and after models are generated\n    async beforeShow(ctx) {}\n    async afterShow(ctx, model) {}\n\n    async beforeIndex(ctx) {}\n    async afterIndex(ctx, collection) {}\n\n    async beforeCreate(ctx, rawModel) {}\n    async afterCreate(ctx, newModel, rawModel) {}\n\n    async beforeUpdate(ctx, oldModel) {}\n    async afterUpdate(ctx, newModel) {}\n\n    async beforeDestroy(ctx, oldModel) {}\n    async afterDestroy(ctx, deadModel) {}\n}\n\ncontroller = new MyController('user', options);\n```\n\n### Name `String`\n\nThe name of the model for the controller to create an API for\n\n### Options `Object`\n\nIs an object with options for ParkesController which is also passed to RestHandler\n\nOption             | Default    | Description\n------------------ | ---------- | ---------------------------------------------------------------------------------------------------\nauthorize          | undefined  | A hook to authorize api calls. The controller will throw if this is not set, set it to false if you don't want any authorization.\nauthorizationScope | undefined  | Callback function for binding dynamic model associations by the `ctx.state.user` value\nmodels             | (required) | Object containing all of your sequelize models (they should have singular names, ie User not Users)\nresourceIdColumn   | 'uuid'     | Name of the column to be used for a resource id by the api\ninclude            | []         | Default includes to be used with find or findAll (can be overridden per call)\nfilterAttributes   | []         | An array of attribute names that can be specified in the HTTP query that will cause a corresponding where clause in the sequelize query\nscopeModels        | []         | The presence of these keys in the query or params will filter findAll queries by joining on that model. eg You could specify ['user'] when defining the posts controller to allow API calls to request all posts by a specific user either via /users/:user/posts or /posts?user=:user\nsearch             | ['name']   | Array of fields to compare ?q= text against\nrestricted         | ['id', 'uuid', 'password', 'permission', 'internal', 'privateKey', 'publicKey', 'userId', 'organisationId'] | Array of fields that may not be changed by create/update requests\nallowed            | []         | Allow one of the default restricted fields without redefining the rest\ndefaultPageLength  | 100        | Default limit for pagination\n\n\n## Hooks\n\nAs shown in the above example, Parkes Controller allows you to bind events to before and after a primary database action occors within a request. This allows you to modify the requests before a response is generated.\n\n### Hook Parameters\n\n\n`ctx` represents a Koa context object, while the second paramater represents a single Sequelize model or collection of models (unless otherwise stated).\n\nHook Name           | Returns             | Description\n------------------- | ------------------- | ---------------------------------------------------------\nasync beforeShow    | `(ctx)`             | Before a single model is fetched\nasync afterShow     | `(ctx, model)`      | After a single model is fetched\nasync beforeIndex   | `(ctx)`             | Before a model collection is fetched\nasync afterIndex    | `(ctx, collection)` | After a model collection is fetched\nasync beforeCreate  | `(ctx, rawModel)`   | Before a model is added to the database (raw model)\nasync afterCreate   | `(ctx, newModel)`   | After a model has been added to the database (full model)\nasync beforeUpdate  | `(ctx, oldModel, rawModel)`   | Before a single model is updated\nasync afterUpdate   | `(ctx, newModel)`   | After a single model is updated\nasync beforeDestroy | `(ctx, oldModel)`   | Before a single model is destroyed (or disabled)\nasync afterDestroy  | `(ctx, deadModel)`  | After a single model is destroyed (or disabled)\n\n## Authorization\n\nA function for authorizing requests with the following signature.\nThis is required. If you really don't want to authorize requests, set this to false.\n\n```javascript\nfunction(ctx, { model, parent, scopes, isPrivate })\n```\n\nParamters | Type                        | Description\n--------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------\nctx       |                             | Context of the Koa request\nmodel     | Sequelize class or instance | The model that the request is accessing, usually the record that is being viewed/updated/deleted. For create this will be the class of the object to be created. See below for index\nparent    | Sequelize instance          | In the case of create it's often necessary to know the record that the object will be a child of before creating the record (eg a user can only add an item to their shop )\nscopes    | String[]                    | Records that a findAll request is being scoped by\nisPrivate | boolean                     | true if the request query contains ?private=1\n\nIn the case of index, `authorize` is called twice, the first time model will be the class to be indexed, allowing you to prevent fetching large arrays from the DB if the user has no access to that model. Once the records are retrieved, model will contain the array of sequelize records.\n\n## ParkesController and RestHandler\n\nParkesController delegates the bulk of the work to RestHandler which handles generating and executing sequelize queries. This allows the controller methods to remain fairly lean - generally they're of the form findRecord, authorize, beforeHook, update, afterHook. If you need more customisation of an action than the hooks can provide, then you can override the controller method and still make use of this.rest to delegate to the RestHandler.\n\n## Event emitters\n\nThe RestHandler provides the EventEmitter interface, `EventEmitter` allowing you to hook non-blocking events in the request. The names and parameters of the events are the same as the hooks (above), but do not block the completion of the request (and so cannot be used to modify the HTTP response).\n\n```javascript\nclass MyController extends ParkesController {\n    constructor(name, options){\n        super(name, options);\n\n        this.rest.on('beforeCreate', (ctx, rawModel) =\u003e {\n            // your non-blocking event code here...\n        });\n    }\n}\n```\n\n\u003e _Please not that unlike `async` hooks, the event emitters should not be used to modify requests or models. As EventEmitter events will not be synchronous with the HTTP request._\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fraisely%2Fparkes-controller","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fraisely%2Fparkes-controller","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fraisely%2Fparkes-controller/lists"}