{"id":14989421,"url":"https://github.com/ysocorp/koa-smart","last_synced_at":"2025-08-22T05:35:15.734Z","repository":{"id":44871843,"uuid":"113304153","full_name":"ysocorp/koa-smart","owner":"ysocorp","description":"A framework base on Koajs2 with Decorator, Params checker and a base of modules (cors, bodyparser, compress, I18n, etc…) to let you develop smart api easily","archived":false,"fork":false,"pushed_at":"2022-03-11T11:04:20.000Z","size":775,"stargazers_count":36,"open_issues_count":5,"forks_count":11,"subscribers_count":6,"default_branch":"develop","last_synced_at":"2025-08-17T16:29:13.501Z","etag":null,"topics":["boilerplate","class","decorators","essential","framework","koa-boilerplate","koa2","koajs","parameters","router"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","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/ysocorp.png","metadata":{"files":{"readme":"README-V1.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-06T10:48:17.000Z","updated_at":"2025-08-04T16:15:48.000Z","dependencies_parsed_at":"2022-09-10T19:11:43.221Z","dependency_job_id":null,"html_url":"https://github.com/ysocorp/koa-smart","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/ysocorp/koa-smart","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysocorp%2Fkoa-smart","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysocorp%2Fkoa-smart/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysocorp%2Fkoa-smart/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysocorp%2Fkoa-smart/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ysocorp","download_url":"https://codeload.github.com/ysocorp/koa-smart/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ysocorp%2Fkoa-smart/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271591297,"owners_count":24786397,"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","status":"online","status_checked_at":"2025-08-22T02:00:08.480Z","response_time":65,"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":["boilerplate","class","decorators","essential","framework","koa-boilerplate","koa2","koajs","parameters","router"],"created_at":"2024-09-24T14:18:20.674Z","updated_at":"2025-08-22T05:35:15.641Z","avatar_url":"https://github.com/ysocorp.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# **KoaSmart** is a framework based on **Koajs2**, which allows you to develop RESTful APIs with : **Class**, **Decorator**, **Params checker**\n\n[![Build Status](https://secure.travis-ci.org/ysocorp/koa-smart.png?branch=master \"Test\")](http://travis-ci.org/ysocorp/koa-smart)\n[![NPM version](http://badge.fury.io/js/koa-smart.png)](https://npmjs.org/package/koa-smart \"View this project on NPM\")\n\nA framework based on [Koajs2](https://github.com/koajs/koa) with **Decorator**, **Params checker** and a **base of modules** ([`cors`](https://www.npmjs.com/package/kcors), [`bodyparser`](https://github.com/koajs/bodyparser), [`compress`](https://github.com/koajs/compress), [`I18n`](https://github.com/koa-modules/i18n), etc... ) to allow you to develop a smart api easily\n```sh\n  export default class RouteUsers extends Route {\n\n    // get route: http://localhost:3000/users/get/:id\n    @Route.Get({\n      path: 'get/:id'\n    })\n    async get(ctx) {\n      const user = await this.models.users.findById(ctx.params.id);\n      this.assert(user, 404, ctx.state.__('User not found'));\n      this.sendOk(ctx, user);\n    }\n\n    // post route: http://localhost:3000/users/add\n    @Route.Post({\n      accesses: [Route.accesses.public],\n      params: { // params to allow: all other params will be rejected\n        email: true, // return an 400 if the body doesn't contain email key\n        name: false,\n      },\n    })\n    async add(ctx) {\n      const body = this.body(ctx); // or ctx.request.body\n      // body can contain only an object with email and name field\n      const user = await this.models.user.create(body);\n      this.sendCreated(ctx, user);\n    }\n\n  }\n```\n\n## Summary\n* What's in this framework ?\n* [Install](#install)\n* [Router with decorator](#router-with-decorator)\n* [Params checker of POST body](#params-checker-of-post-body)\n* [Get Started](#get-started)\n    * [Full example](#full-example)\n* [Add treatment on route](#add-treatment-on-route)\n\n## What is in this framework ?\n\n**This framework gives you the tools to use a set of modules: **\n\n* **For routing**\n    * [`koajs 2`](https://github.com/koajs/koa) as the main, underlying framework\n    * [`kcors`](https://www.npmjs.com/package/kcors) is used to handle cross-domain requests\n    * [`koa2-ratelimit`](https://github.com/ysocorp/koa2-ratelimit) To limit bruteforce requests\n    * [`koa-helmet`](https://www.npmjs.com/package/koa-helmet) helps you secure your api\n    * [`koa-bodyparser`](https://github.com/koajs/bodyparser) to parse request bodies\n    * [`koa-compress`](https://github.com/koajs/compress) to compress the response\n    * [`koa-i18n`](https://github.com/koa-modules/i18n) for Internationalization (I18n)\n* [`@Decorators`](https://babeljs.io/docs/plugins/transform-decorators/) to ensure a better project structure\n* [`moment`](https://github.com/moment/moment) Parse, validate, manipulate, and display dates in javascript.\n* [`lodash`](https://github.com/lodash/lodash) A modern JavaScript utility library delivering modularity, performance, \u0026 extras\n* [`jsonwebtoken`](https://github.com/auth0/node-jsonwebtoken) an implementation of [JSON Web Tokens JWT](https://tools.ietf.org/html/rfc7519)\n\nthe full documentation for this module can be found [here](https://ysocorp.github.io/koa-smart/)\n\n## Install\n`npm install koa-smart`\n\n## Router with decorator\n\n**All routes have to extend the `Route` class in order to be mount**\n\n* **Prefix of routes**\n\n    If you have a route class with the name `RouteMyApi`, all the routes inside said class will be **preceded** by **`/my-api/`**\n\n    * How does it work ?\n        1) the `Route` word is removed\n        2) uppercase letters are replaced with '-'. (essentially converting camelCase into camel-case)\n        **e.g.**: this will add a get route =\u003e http://localhost:3000/my-api/hello\n\n      ```sh\n      export default class RouteMyApi extends Route {\n\n          @Route.Get({})\n          async hello(ctx) {\n              this.sendOk(ctx, ctx.state.__('hello'));\n          }\n\n      }\n      ```\n\n    * Change prefix of all routes in the class: http://localhost:3000/my-prefix/hello\n      ```sh\n      @Route.Route({\n          routeBase: 'my-prefix',\n      })\n      export default class RouteMyApi extends Route {\n\n          @Route.Get({})\n          async hello(ctx) {\n              this.sendOk(ctx, ctx.state.__('hello'));\n          }\n\n      }\n      ```\n\n* **Get route** http://localhost:3000/my-api/hello\n\n  ```sh\n    @Route.Get({})\n    async hello(ctx) {\n      this.sendOk(ctx, null, ctx.state.__('hello'));\n    }\n  ```\n* **Change path** http://localhost:3000/my-api/myroute/15\n\n  ```sh\n    @Route.Get({\n      path: '/myroute/:id'\n    })\n    async hello(ctx) {\n      this.sendOk(ctx, ctx.state.__('hello') + ctx.params.id);\n    }\n  ```\n\n* **Post route** http://localhost:3000/my-api/user-post\n  ```sh\n    @Route.Post({\n        params: { // params to allow: all other params will be rejected\n            email: true, // return a 400 error if the body doesn't contain email key\n            name: false, // optional parameter\n        },\n    })\n    async userPost(ctx) {\n      const body = this.body(ctx);\n      // body can contain only an object with email and name field\n      const user = await this.models.user.create(body);\n      this.sendCreated(ctx, user);\n    }\n  ```\n\n* **Disable route**\n    * **Disable all routes in a class**\n\n    to disable all routes in a class you should add `disable` in the content of your decorator class\n\n    ```sh\n    @Route.Route({\n        disable: true,\n    })\n    export default class RouteMyApi extends Route {\n        // All routes in this class will not be mounted\n    }\n    ```\n\n    * **Disable a specific route**\n\n    to disable a specific route you can add `disable` in the content of your decorator\n\n    ```sh\n    @Route.Get({\n        disable: true, // this route will not be mounted\n    })\n    async hello(ctx) {\n      this.sendOk(ctx, null, ctx.state.__('hello'));\n    }\n    ```\n\n* **RateLimit** : For more infos, see the [`koa2-ratelimit`](https://github.com/ysocorp/koa2-ratelimit) module\n\n  * **Configure**\n\n    ```sh\n    import { App } from 'koa-smart';\n    import { RateLimit, RateLimitStores } from 'koa-smart/middlewares';\n\n    const app = new App({ port: 3000 });\n\n    // Set Default Option\n    const store = new RateLimitStores.Memory() OR new RateLimitStores.Sequelize(sequelizeInstance)\n    RateLimit.defaultOptions({\n        message: 'Too many requests, get out!',\n        store: store, // By default it will create MemoryStore\n    });\n\n    // limit 100 accesses per min on your API\n    app.addMiddlewares([\n      // ...\n      RateLimit.middleware({ interval: { min: 1 }, max: 100 }),\n      // ...\n    ]);\n    ```\n\n  * **RateLimit On Decorator**\n\n    Single RateLimit\n\n    ```sh\n    @Route.Get({ // allow only 100 requests per day to /view\n        rateLimit: { interval: { day: 1 }, max: 100 },\n    })\n    async view(ctx) {\n      this.sendOk(ctx, null, ctx.state.__('hello'));\n    }\n    ```\n\n    Multiple RateLimit\n\n    ```sh\n    // Multiple RateLimit\n    @Route.Get({\n        rateLimit: [\n            { interval: { day: 1 }, max: 100 }, // allow only 100 requests per day\n            { interval: { min: 2 }, max: 40 }, // allow only 40 requests in 2 minutes\n        ],\n    })\n    async hello(ctx) {\n      this.sendOk(ctx, null, ctx.state.__('hello'));\n    }\n    ```\n\n* **middlewares of a Class**\n\n  ```sh\n  @Route.Route({\n      middlewares: [ // Array of middlewares\n        async (ctx, next) =\u003e {\n          console.log('I will be call before all route in this class');\n          await next();\n        },\n      ],\n  })\n  class RouteMiddlewares extends Route {\n      async view(ctx, next) {\n        console.log('I will be call after middlewares of class');\n        this.sendOk(ctx, null, ctx.state.__('hello'));\n      }\n  }\n  ```\n\n* **middlewares of a specific route**\n\n  ```sh\n  @Route.Get({\n      middlewares: [ // Array of middlewares\n        async (ctx, next) =\u003e {\n          console.log('I will be call before the route but after middlewares of class');\n          await next();\n        },\n      ],\n  })\n  async view(ctx, next) {\n      console.log('I will be call after middlewares of the class and route');\n      this.sendOk(ctx, null, ctx.state.__('hello'));\n  }\n  ```\n\n## Params checker of POST body\n\n* **all other fields which aren't in the params object will be rejected**\n* simplified writing\n\n  ```sh\n    params: ['email', 'name']\n    // is equal to\n    params: {\n      email: false,\n      name: false,\n    }\n    // is equal to\n    params: {\n      email: {\n        __force: false,\n      },\n      name: false,\n    }\n  ```\n* **more option:**\n    * `__force` [boolean] tells whether a field is required or not\n    * `__func` an `Array\u003cFunction\u003e` to be executed on the field one by one in order to validate / transform it\n    * Eg:\n\n        ```sh\n        params: {\n          name: {\n            __force: false,\n            __func: [\n                utils.trim,\n                utilsParam.test(utils.notEmpty), // return 400 if empty\n                utils.capitalize,\n                (elem, route, { ctx, body, keyBody }) =\u003e {\n                  return elem.trim();\n                },\n                // do whatever you want...\n            ],\n          },\n        },\n        ```\n* **Eg: object nested inside another object:**\n\n    ```sh\n    params: {\n      user: {\n        __force: true,\n        name: {\n          __force: true,\n          __func: [utils.trim],\n        },\n        password: true,\n        address: {\n          __force: true,\n          country: true,\n          street: true,\n        }\n      },\n      date: false,\n    },\n    ```\n\n## Get Started ([quick-start boilerplate](https://github.com/ysocorp/koa-smart-light-example))\n\nin order to get started quickly, look at [this boilerplate](https://github.com/ysocorp/koa-smart-light-example), or follow the instructions below:\n\n  * import the app and your middlewares\n\n    ```sh\n    import { join } from 'path';\n    // import the app\n    import { App } from 'koa-smart';\n    // import middlewares koa-smart give you OR others\n    import {\n      bodyParser,\n      compress,\n      cors,\n      handleError,\n      RateLimit,\n      ...\n    } from 'koa-smart/middlewares';\n    ```\n\n  * create an app listening on port 3000\n\n    ```sh\n    const myApp = new App({\n      port: 3000,\n    });\n    ```\n\n  * add your middlewares\n\n    ```sh\n    myApp.addMiddlewares([\n      cors({ credentials: true }),\n      helmet(),\n      bodyParser(),\n      handleError(),\n      RateLimit.middleware({ interval: { min: 1 }, max: 100 }),\n      ...\n    ]);\n    ```\n\n  * add your routes\n    mount a folder with a prefix (all file who extends from `Route` will be added and mounted)\n\n    ```sh\n        myApp.mountFolder(join(__dirname, 'routes'), '/');\n    ```\n\n  * Start your app\n\n    ```sh\n    myApp.start();\n    ```\n\n### Full example\n\n  * Basic one\n\n    ```sh\n    import { join } from 'path';\n    // import the app\n    import { App } from 'koa-smart';\n    // import middlewares koa-smart give you OR others\n    import {\n      i18n,\n      bodyParser,\n      compress,\n      cors,\n      helmet,\n      addDefaultBody,\n      handleError,\n      logger,\n      RateLimit,\n    } from 'koa-smart/middlewares';\n\n    const myApp = new App({\n      port: 3000,\n    });\n\n    myApp.addMiddlewares([\n      cors({ credentials: true }),\n      helmet(),\n      bodyParser(),\n      i18n(myApp.app, {\n        directory: join(__dirname, 'locales'),\n        locales: ['en', 'fr'],\n        modes: ['query', 'subdomain', 'cookie', 'header', 'tld'],\n      }),\n      handleError(),\n      logger(),\n      addDefaultBody(),\n      compress({}),\n      RateLimit.middleware({ interval: { min: 1 }, max: 100 }),\n    ]);\n\n    // mount a folder with an prefix (all file who extends from `Route` will be add and mount)\n    myApp.mountFolder(join(__dirname, 'routes'), '/');\n\n    // start the app\n    myApp.start();\n    ```\n\n  * Other example who Extends class App\n\n    ```sh\n    import { join } from 'path';\n    // import the app\n    import { App } from 'koa-smart';\n    // import middlewares koa-smart give you OR others\n    import {\n      i18n,\n      bodyParser,\n      compress,\n      cors,\n      helmet,\n      addDefaultBody,\n      handleError,\n      logger,\n      RateLimit,\n    } from 'koa-smart/middlewares';\n\n    // create an class who extends from App class\n    export default class MyApp extends App {\n      constructor() {\n        super({ port: 3000 });\n      }\n\n      async start() {\n        // add your Middlewares\n        super.addMiddlewares([\n          cors({ credentials: true }),\n          helmet(),\n          bodyParser(),\n          i18n(this.app, {\n            directory: join(__dirname, 'locales'),\n            locales: ['en', 'fr'],\n            modes: ['query', 'subdomain', 'cookie', 'header', 'tld'],\n          }),\n          handleError(),\n          logger(),\n          addDefaultBody(),\n          compress({}),\n          RateLimit.middleware({ interval: { min: 1 }, max: 100 }),\n        ]);\n\n        // mount a folder with an prefix (all file who extends from `Route` will be add and mount)\n        super.mountFolder(join(__dirname, 'routes'));\n        return super.start();\n      }\n    }\n\n    // start the app\n    const myApp = new MyApp();\n    myApp.start();\n    ```\n\n## Add treatment on route\n  **you can add you own treatment and attribute to the route.**\n\n  In this example we will see how you can manage **accesses** to your route in 2 steps  \n\n  1. Extends `Route` Class and overload  `beforeRoute` methode\n\n  ```sh\n  export default class MyRoute extends Route {\n    static accesses = {\n      public: -1,\n      connected: 100,\n      admin: GROUPS.ADMIN_ID,\n      client: GROUPS.CLIENT_ID,\n      // whatever ...\n    };\n\n    // overload beforeRoute\n    async beforeRoute(ctx, infos, next) {\n      // infos.options content all the param give to the route\n\n      if (this.mlCanAccessRoute(ctx, infos.options)) { // test if you can access\n        this.throw(StatusCode.forbidden, ctx.state.__('Forbidden access'));\n      }\n      // call the super methode\n      await super.beforeRoute(ctx, infos, next);\n    }\n\n    mlCanAccessRoute(ctx, { accesses }) {\n      if (accesses \u0026\u0026 Array.isArray(accesses)) {\n        const { user } = ctx.state;\n        return accesses.includes(Route.accesses.public) ||\n          (!!user \u0026\u0026 (\n            accesses.includes(Route.accesses.connected) ||\n            user.usergroup_id === Route.accesses.admin ||\n            accesses.includes(user.usergroup_id)\n          ));\n      }\n      return false;\n    }\n  }\n\n  ```\n\n  2. Create a route with access \n\n\n  ```sh\n  export default class RouteMyApi extends MyRoute {\n    constructor(params) {\n      super({ ...params });\n    }\n\n    @Route.Get({ accesses: [MyRoute.accesses.public] })\n    async publicRoute(ctx) {\n      this.sendOk(ctx, ctx.i18n.__('I can be call by any one'));\n    }\n\n    @Route.Get({ accesses: [MyRoute.accesses.client] })\n    async clientRoute(ctx) {\n      this.sendOk(ctx, ctx.i18n.__('I can be call by only client user'));\n    }\n\n    @Route.Get({ accesses: [MyRoute.accesses.admin] })\n    async adminRoute(ctx) {\n      this.sendOk(ctx, ctx.state.__('I can be call by only admin user'));\n    }\n\n    @Route.Get({ accesses: [MyRoute.accesses.client, MyRoute.accesses.admin] })\n    async adminRoute(ctx) {\n      this.sendOk(ctx, ctx.state.__('I can be call by client and admin user'));\n    }\n\n    @Route.Get({ accesses: [MyRoute.accesses.connected] })\n    async adminRoute(ctx) {\n      this.sendOk(ctx, ctx.state.__('I can be call by all connected users'));\n    }\n  }\n  ```\n\n## License\n\n  MIT © [YSO Corp](http://ysocorp.com/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fysocorp%2Fkoa-smart","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fysocorp%2Fkoa-smart","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fysocorp%2Fkoa-smart/lists"}