{"id":19596247,"url":"https://github.com/chrisalderson/pop-api","last_synced_at":"2026-05-13T18:37:28.499Z","repository":{"id":135893705,"uuid":"110229994","full_name":"ChrisAlderson/pop-api","owner":"ChrisAlderson","description":"The base modules for popcorn-api","archived":false,"fork":false,"pushed_at":"2017-11-28T15:57:38.000Z","size":150,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-19T21:45:29.683Z","etag":null,"topics":["middleware","popcorn","popcorn-api","popcorn-time"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ChrisAlderson.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","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":"2017-11-10T09:40:21.000Z","updated_at":"2021-02-13T14:43:33.000Z","dependencies_parsed_at":null,"dependency_job_id":"4b14b581-66a4-49ae-827e-42ddf4a15200","html_url":"https://github.com/ChrisAlderson/pop-api","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/ChrisAlderson/pop-api","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChrisAlderson%2Fpop-api","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChrisAlderson%2Fpop-api/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChrisAlderson%2Fpop-api/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChrisAlderson%2Fpop-api/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ChrisAlderson","download_url":"https://codeload.github.com/ChrisAlderson/pop-api/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ChrisAlderson%2Fpop-api/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32995909,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-13T13:14:54.681Z","status":"ssl_error","status_checked_at":"2026-05-13T13:14:51.610Z","response_time":115,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["middleware","popcorn","popcorn-api","popcorn-time"],"created_at":"2024-11-11T08:52:33.095Z","updated_at":"2026-05-13T18:37:28.468Z","avatar_url":"https://github.com/ChrisAlderson.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pop-api\n\n[![Build Status](https://travis-ci.org/ChrisAlderson/pop-api.svg?branch=development)](https://travis-ci.org/ChrisAlderson/pop-api)\n[![Coverage Status](https://coveralls.io/repos/github/ChrisAlderson/pop-api/badge.svg?branch=development)](https://coveralls.io/github/ChrisAlderson/pop-api?branch=development)\n[![Dependency Status](https://david-dm.org/ChrisAlderson/pop-api.svg)](https://david-dm.org/ChrisAlderson/pop-api)\n[![devDependencies Status](https://david-dm.org/ChrisAlderson/pop-api/dev-status.svg)](https://david-dm.org/ChrisAlderson/pop-api?type=dev)\n\n## Features\n\nThe pop-api project aims to provide the core modules for the\n[`popcorn-api`](https://github.com/popcorn-official/popcorn-api) project, but\ncan also be used for other purposes by using middleware.\n - Cli middleware for reading user input with [`commander.js`](https://github.com/js/commander.js).\n - Database middleware for connection to MongoDB through [`mongoose`](https://github.com/Automattic/mongoose).\n - Logging of routes and other information using [`winston`](https://github.com/winstonjs/winston).\n - Uses [`express`](https://github.com/expressjs/express) under the hood with:\n   - Body middleware\n   - Error handling\n   - Security middleware\n - Interface for registering routes for [`express`](https://github.com/expressjs/express).\n - Data Access Layer (DAL) class for standard CRUD operations.\n - Route controller to handle routes for your content.\n\n## Installation\n\n```\n $ npm install --save pop-api\n```\n\n## Usage\n\n### Basic setup\n\nFor your basic setup you have to create a controller which will handle the\nroutes. Your controller needs to extend from the `IController` interface to\nimplement the `registerRoutes` method which will be called during the setup.\n\nThe route controller below will be created with a constructor which takes an\nobject as the parameter. This example will register a `GET /hello` route and\nsends a JSON object as a response with a greeting to the name provided by the\nobject from the constructor.\n\n```js\n// ./MyRouteController.js\nimport { IController } from 'pop-api'\n\n// Extend your route controller from the 'IController' interface.\nexport default class MyRouteController extends IController {\n\n  // The constructor takes an object as the parameter.\n  constructor({name}) {\n    super()\n\n    this.name = name\n  }\n\n  // Implement the 'registerRoutes' method from the 'IController interface.\n  registerRoutes(router, PopApi) {\n    router.get('/hello', this.getHello.bind(this))\n  }\n\n  // Router middleware to execute on the 'GET /hello' route.\n  getHello(req, res, next) {\n    return res.json({\n      message: `Hello, ${this.name}`\n    })\n  }\n\n}\n```\n\nTo initialize the API we create an array of the route controllers and their\nconstructor arguments we want to register. Then we just call the `init` method\nwith the route controllers array, and the name and version your API (needed for\nthe Cli). The API should run by default on port `5000`.\n\n```js\n// ./index.js\nimport { PopApi } from 'pop-api'\nimport MyRouteController from './MyRouteController'\nimport { name, version } from './package.json'\n\n(async () =\u003e {\n  try {\n    // Define the controllers you want to use.\n    const controllers = [{\n      Controller: MyRouteController,  // The controller to register.\n      args: {                         // The arguments passed down to the\n        name: 'John'                  // The additional arguments to pass to\n                                      // your route controller.\n      }\n    }]\n\n    // Initiate your API with the necessary parameters.\n    await PopApi.init({                \n      controllers,  // The controllers to register.\n      name,         // The name of your API.\n      version       // The version of your API.\n    })\n    // API is available on port 5000.\n    // GET http://localhost:5000/hello -\u003e { message: 'Hello, John' }\n  } catch (err) {\n    console.log(error)\n  }\n})()\n```\n\n### Advanced setup\n\nFor the advanced setup we will create a model using\n[`mongoose`](https://github.com/Automattic/mongoose) and a route controller\nextending from `BaseContentController`. Below we create a simple\n[`mongoose`](https://github.com/Atomattic/mongoose) model.\n\n```js\n// ./myModel.js\nimport mongoose, { Schema } from 'mongoose'\n\n// Create a simple mongoose schema.\nconst mySchema =  new Schema({\n  _id: {\n    type: String,\n    required: true,\n    index: {\n      unique: true\n    }\n  },\n  slug: {\n    type: String,\n    required: true\n  },\n  name: {\n    type: String,\n    required: true\n  }\n})\n\n// Create a model from the schema.\nexport default mongoose.model('MyModel', mySchema)\n```\n\nBefore we create a route controller we need a `ContentService` object which \nats as the DAL for the CRUD operations. The `ContentService` object will be\nused as a parameter when creating the route controller. \n\n```js\n// ./myService.js\nimport { ContentService } from 'pop-api'\nimport MyModel from './myModel'\n\nconst myService = new ContentService({\n  Model: MyModel,           // The model for the service.\n  basePath: 'example',      // The base path to register the routes to.\n  projection: { name: 1 },  // Projection used to display multiple items.\n  query: {}                 // (Optional) The default query to fetch items.\n})\n\n// Methods available: \n// - myService.getContents([base])\n// - myService.getPage([sort, page, query])\n// - myService.getContent(id, [projection])\n// - myService.createContent(obj)\n// - myService.createMany(arr)\n// - myService.updateContent(id, obj)\n// - myService.updateMany(arr)\n// - myService.deleteContent(id)\n// - myService.deleteMany(arr)\n// - myService.getRandomContent()\n\nexport default myService\n```\n\nNow we create a route controller which extends from `BaseContentController`.\nThis route controller can be used on it's own o classes can extend from it to\nimplement new behaviour or override existing behaviour. The\n`BaseContentController` class already implements the `registerRoutes` method\nfrom `IController` and adds the following routes to your API (note the base\npath will be taken from the `ContentService`):\n - GET    `/examples`        Get a list of a available pages to get.\n - GET    `/examples/:page`  Get a page of models.\n - GET    `/example/:id`     Get a single model.\n - POST   `/examples`        Create a new model.\n - PUT    `/example/:id`     Update an existing model. \n - DELETE `/example/:id`     Delete a model.\n - GET    `/random/example`  Get a random model.\n\nThe following example extends from `BaseContentController`, registers the default\nroutes and implements a `GET /hello` route.\n\n```js\n// ./MyRouteController.js\nimport { BaseContentConroller } from 'pop-api'\n\n// Extend from the `BaseContentController` which has defaults methods for CRUD\n// operations.\nexport default class MyRouteController extends BaseContentController {\n\n  // The constructor of `BaseContentConroller` needs an instance of\n  // `ContentService` which we will create later. It can also take additional\n  // parameters for your own implementation.\n  constructor({service, name}) {\n    super({service})\n\n    this.name = name\n  }\n\n  // Implement the 'registerRoutes' method from the 'IController interface.\n  registerRoutes(router, PopApi) {\n    // Call the `registerRoutes` method from the `BaseContentController` class\n    // to register the default routes.\n    super.registerRoutes(router, PopApi)\n\n    // And add additional routes for your route controller.\n    router.get('/hello', this.getHello.bind(this))\n  }\n\n  // Router middleware to execute on the 'GET /hello' route.\n  getHello(req, res, next) {\n    return res.json({\n      message: `Hello, ${this.name}`\n    })\n  }\n\n}\n```\n\nNow to initial your API we create a list of route controllers we want to\nregister with their constructor parameters. This example also shows additional\nparameters to pass down to the `init` method of the `PopApi` instance.\n\n```js\n// ./index.js\nimport { PopApi, ContentService } from 'pop-api'\nimport MyRouteController from './MyRouteController'\nimport myService from './myService'\nimport { name, version } from './package.json'\n\n(async () =\u003e {\n  try {\n    // Define the controllers you want to use.\n    const controllers = [{\n      Controller: MyRouteController,  // The controller to register.\n      args: {                         // The arguments passed down to the\n                                      // constructor of the controller.\n        service: myService,           // The content service for the\n                                      // BaseContentController.\n        name: 'John'                  // The additional arguments to pass to\n                                      // your route controller.\n      }\n    }]\n\n    // Initiate your API with optional parameters.\n    await PopApi.init({\n      controllers,             // The controllers to register.\n      name,                    // The name of your API.\n      version,                 // The version of your API.\n      logDir: join(...[        // (Optional) The directory to store the log\n                               // files in. Defaults to `./tmp`.\n        __basedir,\n        '..',\n        'tmp'\n      ]),\n      hosts: ['11.11.11.11'],  // (Optional) The hosts to connect to for\n                               // MongoDB. Defaults to `['localhost']`.\n      dbPort: 27019,           // (Optional) The port of MongoDB to connect to\n                               // Defaults to `27017`.\n      username: 'myUsername',  // (Optional) The username to connect to.\n                               // MongoDB. Defaults to `null`.\n      password: 'myPassword',  // (Optional) The password to connect to.\n                               // MongoDB. Defaults to `null`.\n      serverPort: 8080,        // (Optional) The port to run your API on.\n                               // Defaults to `5000`.\n      workers: 4               // The amount of workers to fork for the server.\n                               // Defaults to `2`.\n    })\n    // API is available on port 8080.\n\n    // GET http://localhost:8080/hello\n    // { \"message\": \"Hello, John\" }\n\n    // GET http://localhost:8080/examples\n    // [\"/examples/1', \"/examples/2\"]\n\n    // GET http://localhost:8080/examples/1\n    // [\n    //   { \"_id\": \"578df3efb618f5141202a196\", \"name\": \"John\" },\n    //   { \"_id\": \"578df3efb618f5141202a196\", \"name\": \"Mary\" }\n    // ]\n\n    // GET http://localhost:8080/example/578df3efb618f5141202a196\n    // { \"_id\": \"578df3efb618f5141202a196\", \"name\": \"John\", \"slug\": \"john\" }\n\n    // POST http://localhost:8080/examples\n    // body: { \"name\": \"Mary\", \"slug\": \"mary\" }\n    // { \"_id\": \"578df3efb618f5141202a196\", \"name\": \"Mary\", \"slug\": \"mary\" }\n\n    // PUT http://localhost:8080/example/578df3efb618f5141202a196\n    // body: { \"name\": \"James\", \"slug\": \"james\" }\n    // { \"_id\": \"578df3efb618f5141202a196\", \"name\": \"James\", \"slug\": \"james\" }\n\n    // DELETE http://localhost:8080/example/578df3efb618f5141202a196\n    // { \"_id\": \"578df3efb618f5141202a196\", \"name\": \"James\", \"slug:\" :james\" }\n\n    // GET http://localhost:8080/random/example -\u003e { }\n    // { \"_id\": \"578df3efb618f5141202a196\", \"name\": \"Mary\", \"slug\": \"mary\" }\n  } catch (err) {\n    console.log(error)\n  }\n})()\n```\n\n## API\n\n - [Cli](#cli)\n - [Database](#database)\n - [HttServer](#httpserver)\n - [Logger](#logger)\n - [Routes](#routes)\n - [Custom Middleware](#custom-middleware)\n\n### Cli\n\nThe `Cli` middleware uses [`commander.js`](https://github.com/tj/commander.js)\nmodules to parse the input of the user. The middleware itself doesn't bind\nanything to the `PopApi` instance, instead it parses the input and run the API\naccordingly.\n\n```js\nimport { PopApi, Cli, Logger } from 'pop-api'\nimport { name, version } from './package.json'\n\nconst cliOpts = {\n  name,               // The name of your application\n  version,            // The version of your application\n  argv: process.argv  // The arguments to parse\n}\nPopApi.use(Cli, cliOpts)\n\n// Parsed the input given and binds options for the `Logger` middleware.\n// See the documentation for the `Logger` middleware for more options.\nconst { pretty, quiet } = PopApi.loggerArgs\nPopApi.use(Logger, {\n  pretty,\n  quiet\n})\n```\n\n### Database\n\nThe `Database` middleware bind the `database` key to the `PopApi` instance.\nThis middleware allows you to `connect()` and `disconnect()` from MongoDB\nthrough [`mongoose`](https://github.com/Automattic/mongoose), and you can\nexport and import a collection with the\n`exportCollection(collection, outputFile)` and\n`importCollection(collection, jsonFile)` methods. The example below uses a\n`.env` file to store the optional `username` and `password` values to establish\na connection with MongoDB.\n\n```dosini\n# .env\n# (Optional) Assuming you use the `dotenv` modules to get your username and\n# password for the database connection\nDATABASE_USERNAME=myUsername\nDATABASE_PASSWORD=myPassword\n```\n\nNow setup the `Database` middleware:\n\n```js\n// (Optional) Assuming you use the `dotenv` modules to get your username and\n// password for the database connection\nimport 'dotenv/config'\nimport { PopApi, Database } from 'pop-api'\nimport MyModel from './MyModel'\nimport { name } from './package.json'\n\nconst databaseOpts = {\n  database: name,                           // The name of the database.\n  hosts: ['localhost'],                     // A lst of hosts to connect to.\n  dbPort: 27017,                            // (Optional) The port of MongoDB.\n  username: process.env.DATABASE_USERNAME,  // (Optional) The username to\n                                            // connect to the hosts.\n  password: process.env.DATABASE_PASSWORD   // (Optional) The password to\n                                            // connect to the hosts.\n}\nPopApi.use(Database, databaseOpts)\n\n// The database middleware can now be used to connect to the MongoDB database.\nPopApi.database.connect()\n  .then(() =\u003e {\n   // Connection successful!\n   return new MyModel({\n     key: 'value'\n   }).save()\n  })\n  .catch(err =\u003e {\n    // Handle error\n  })\n  .then(() =\u003e {\n    // Disconnect from MongoDB.\n    PopApi.database.disconnect()\n  })\n```\n\n### HttpServer\n\nThe `HttpServer` middleware forks workers so heavy load process can run on\ndifferent child processes. It also makes the\n [`express`](https://github.com/expressjs/express) app listen on a port.\n\n```js\nimport { PopApi, HttpServer } from 'pop-api'\n\nconst httpServerOpts = {\n  app: PopApi.app,   // The express instance from PopApi.\n  serverPort: 5000,  // The port your API will be running on.\n  workers: 2         // The amount of workers to fork.\n}\nPopApi.use(HttpServer, httpServerOpts)\n// Doesn't bind anything to the PopApi instance, just forks the workers and\n// makes the app listen on your configured port.\n```\n\n### Logger\n\nThe `Logger` middleware uses the\n[`winston`](https://github.com/winstonjs/winston) module to create a global\n`logger` object. This `logger` object has various levels to log, such as\n`debug`, `info`, `warn` and `error`. This middleware also binds an\n[`express`](https://github.com/expressjs/express) middleware function to log\nthe routes.\n\n```js\nimport { PopApi, Logger } from 'pop-api'\nimport { join } from 'path'\nimport { name } from './package.json'\n\nconst loggerOpts = {\n  name,                                 // The name of the log file.\n  logDir: join(...[__basedir, 'tmp']),  // The directory to store the logs in.\n  pretty: true,                         // (Optional) Pretty output mode.\n  quiet: false                          // (Optional) Quiet mode for no output.\n}\nPopApi.use(Logger, loggerOpts)\n\nlogger.info('\\logger\\' will be a global object')\n// Other log levels you can use are:\n//  - logger.debug()\n//  - logger.info()\n//  - logger.warn()\n//  - logger.error()\n\n// Log middleware for logging routes, used by the `Routes` middleware, or set\n// it yourself.\nconst { expressLogger } = PopApi\nPopApi.app.use(expressLogger)\n```\n\n### Routes\n\nThe `Routes` middleware configures the\n[`express`](https://github.com/expressjs/express) instance. It sets up the\n[`body-parser`](https://github.com/expressjs/body-parser) and\n[`compression`](https://github.com/exprssjs/compression) middleware, as well as\nthe error and security middleware. Thirdly it registers the controllers with\ntheir routes.\n\n```js\nimport { PopApi, Routes } from 'pop-api'\nimport MyRouteController from './MyRouteController'\n\nconst routesOpts = {\n  app: PopApi.app,                  // The express instance from PopApi.\n  controllers: [{                   // A list of controllers to register.\n    Controller: MyRouteController,  // The controller you want to register.\n    args: {}                        // The arguments to pass down to the\n                                    // MyRouteController.\n  }]\n}\nPopApi.use(Routes, routesOpts)\n// Doesn't bind anything to the PopApi instance, just configures the middleware\n// for express and registers the controllers.\n```\n\n### Custom Middleware\n\nThe `init` method will register the default Cli, Database, Logger, Routes and\nServer middleware, but you can also extends the functionality of `pop-api` by\nusing your own middleware. In the middleware example below we create a\nmiddleware class which will only hold a simple greeting.\n\n```js\n// ./MyMiddleware.js\nexport default class MyMiddleware {\n\n  // The first parameter will be the 'PopApi' instance, the second will be an\n  // object you can use to configure your middleware.\n  constructor(PopApi, {name}) {\n    this.name = name\n\n    PopApi.myMiddleware = this.myMiddleware()\n  }\n\n  myMiddleware() {\n    return `Hello, ${this.name}`\n  }\n\n}\n```\n\nTo use the middleware we created you call the `use` method. The first parameter\nwill be the middleware class you want to create, the second parameter is\noptional, but can be used to configure your middleware.\n\n```js\n// ./index.js\nimport { PopApi } from 'pop-api'\nimport MyMiddleware from './MyMiddleware'\n\n// Use the middleware you created.\nPopApi.use(MyMiddleware, {\n  name: 'John'\n})\n\n// The middleware will be bound to the 'PopApi' instance.\nconst greeting = PopApi.myMiddleware // Hello, John\n```\n\n## License\n\nMIT License\n\nCopyright (c) 2017 - pop-api - Released under the [MIT license](LICENSE.txt).\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchrisalderson%2Fpop-api","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchrisalderson%2Fpop-api","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchrisalderson%2Fpop-api/lists"}