New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

koa-smart

Package Overview
Dependencies
Maintainers
1
Versions
33
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

koa-smart

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

  • 1.1.1
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
34
decreased by-63.83%
Maintainers
1
Weekly downloads
 
Created
Source

KoaSmart is a framework based on Koajs2, which allows you to develop RESTful APIs with : Class, Decorator, Params checker

Build Status NPM version

A framework based on Koajs2 with Decorator, Params checker and a base of modules (cors, bodyparser, compress, I18n, etc... ) to allow you to develop a smart api easily

  export default class RouteUsers extends Route {

    // get route: http://localhost:3000/users/get/:id
    @Route.Get({
      path: 'get/:id'
    })
    async get(ctx) {
      const user = await this.models.users.findById(ctx.params.id);
      this.assert(user, 404, ctx.state.__('User not found'));
      this.sendOk(ctx, user);
    }

    // post route: http://localhost:3000/users/add
    @Route.Post({
      accesses: [Route.accesses.public],
      params: { // params to allow: all other params will be rejected
        email: true, // return an 400 if the body doesn't contain email key
        name: false,
      },
    })
    async add(ctx) {
      const body = this.body(ctx); // or ctx.request.body
      // body can contain only an object with email and name field
      const user = await this.models.user.create(body);
      this.sendCreated(ctx, user);
    }

  }

Summary

What is in this framework ?

**This framework gives you the tools to use a set of modules: **

Install

npm install koa-smart

Router with decorator

All routes have to extend the Route class in order to be mount

  • Prefix of routes

    If you have a route class with the name RouteMyApi, all the routes inside said class will be preceded by /my-api/

    • How does it work ?

      1. the Route word is removed
      2. uppercase letters are replaced with '-'. (essentially converting camelCase into camel-case) e.g.: this will add a get route => http://localhost:3000/my-api/hello
      export default class RouteMyApi extends Route {
      
          @Route.Get({})
          async hello(ctx) {
              this.sendOk(ctx, ctx.state.__('hello'));
          }
      
      }
      
    • Change prefix of all routes in the class: http://localhost:3000/my-prefix/hello

      @Route.Route({
          routeBase: 'my-prefix',
      })
      export default class RouteMyApi extends Route {
      
          @Route.Get({})
          async hello(ctx) {
              this.sendOk(ctx, ctx.state.__('hello'));
          }
      
      }
      
  • Get route http://localhost:3000/my-api/hello

      @Route.Get({})
      async hello(ctx) {
        this.sendOk(ctx, null, ctx.state.__('hello'));
      }
    
  • Change path http://localhost:3000/my-api/myroute/15

      @Route.Get({
        path: '/myroute/:id'
      })
      async hello(ctx) {
        this.sendOk(ctx, ctx.state.__('hello') + ctx.params.id);
      }
    
  • Post route http://localhost:3000/my-api/user-post

      @Route.Post({
          params: { // params to allow: all other params will be rejected
              email: true, // return a 400 error if the body doesn't contain email key
              name: false, // optional parameter
          },
      })
      async userPost(ctx) {
        const body = this.body(ctx);
        // body can contain only an object with email and name field
        const user = await this.models.user.create(body);
        this.sendCreated(ctx, user);
      }
    
  • Disable route

    • Disable all routes in a class

    to disable all routes in a class you should add disable in the content of your decorator class

    @Route.Route({
        disable: true,
    })
    export default class RouteMyApi extends Route {
        // All routes in this class will not be mounted
    }
    
    • Disable a specific route

    to disable a specific route you can add disable in the content of your decorator

    @Route.Get({
        disable: true, // this route will not be mounted
    })
    async hello(ctx) {
      this.sendOk(ctx, null, ctx.state.__('hello'));
    }
    
  • RateLimit : For more infos, see the koa2-ratelimit module

    • Configure

      import { App } from 'koa-smart';
      import { RateLimit, RateLimitStores } from 'koa-smart/middlewares';
      
      const app = new App({ port: 3000 });
      
      // Set Default Option
      const store = new RateLimitStores.Memory() OR new RateLimitStores.Sequelize(sequelizeInstance)
      RateLimit.defaultOptions({
          message: 'Too many requests, get out!',
          store: store, // By default it will create MemoryStore
      });
      
      // limit 100 accesses per min on your API 
      app.addMiddlewares([
        // ...
        RateLimit.middleware({ interval: { min: 1 }, max: 100 }), 
        // ...
      ]);
      
    • RateLimit On Decorator

      Single RateLimit

      @Route.Get({ // allow only 100 requests per day to /view
          rateLimit: { interval: { day: 1 }, max: 100 },
      })
      async view(ctx) {
        this.sendOk(ctx, null, ctx.state.__('hello'));
      }
      

      Multiple RateLimit

      // Multiple RateLimit
      @Route.Get({
          rateLimit: [
              { interval: { day: 1 }, max: 100 }, // allow only 100 requests per day
              { interval: { min: 2 }, max: 40 }, // allow only 40 requests in 2 minutes
          ],
      })
      async hello(ctx) {
        this.sendOk(ctx, null, ctx.state.__('hello'));
      }
      
  • middlewares of an Class

    @Route.Route({
        middlewares: [ // Array of middlewares
          async (ctx, next) => {
            console.log('I will be call before all route in this class');
            await next();
          },
        ],
    })
    class RouteMiddlewares extends Route {
        async view(ctx, next) {
          console.log('I will be call after middlewares of class');
          this.sendOk(ctx, null, ctx.state.__('hello'));
        }
    }
    
  • middlewares of a specific route

    @Route.Get({
        middlewares: [ // Array of middlewares
          async (ctx, next) => {
            console.log('I will be call before the route but after middlewares of class');
            await next();
          },
        ],
    })
    async view(ctx, next) {
        console.log('I will be call after middlewares of the class and route');
        this.sendOk(ctx, null, ctx.state.__('hello'));
    }
    

Params checker of POST body

  • all other fields which aren't in the params object will be rejected

  • simplified writing

      params: ['email', 'name']
      // is equal to
      params: {
        email: false,
        name: false,
      }
      // is equal to
      params: {
        email: {
          __force: false,
        },
        name: false,
      }
    
  • more option:

    • __force [boolean] tells whether a field is required or not

    • __func an Array<Function> to be executed on the field one by one in order to validate / transform it

    • Eg:

      params: {
        name: {
          __force: false,
          __func: [
              utils.trim,
              utilsParam.test(utils.notEmpty), // return 400 if empty
              utils.capitalize,
              (elem, route, { ctx, body, keyBody }) => {
                return elem.trim();
              },
              // do whatever you want...
          ],
        },
      },
      
  • Eg: object nested inside another object:

    params: {
      user: {
        __force: true,
        name: {
          __force: true,
          __func: [utils.trim],
        },
        password: true,
        address: {
          __force: true,
          country: true,
          street: true,
        }
      },
      date: false,
    },
    

Get Started (quick-start boilerplate)

in order to get started quickly, look at this boilerplate, or follow the following instructions:

  • import the app and your middlewares

    import { join } from 'path';
    // import the app
    import { App } from 'koa-smart';
    // import middlewares koa-smart give you OR others
    import {
      bodyParser, 
      compress,
      cors,
      handleError,
      RateLimit,
      ...
    } from 'koa-smart/middlewares';
    
  • create an app listening on port 3000

    const myApp = new App({
      port: 3000,
    });
    
  • add your middlewares

    myApp.addMiddlewares([
      cors({ credentials: true }),
      helmet(),
      bodyParser(),
      handleError(),
      RateLimit.middleware({ interval: { min: 1 }, max: 100 }),
      ...
    ]);
    
  • add your routes mount a folder with a prefix (all file who extends from Route will be added and mounted)

        myApp.mountFolder(join(__dirname, 'routes'), '/');
    
  • Start your app

    myApp.start();
    

Full example

  • Basic one

    import { join } from 'path';
    // import the app
    import { App } from 'koa-smart';
    // import middlewares koa-smart give you OR others
    import { 
      i18n,
      bodyParser, 
      compress,
      cors,
      helmet,
      addDefaultBody,
      handleError,
      logger,
      RateLimit,
    } from 'koa-smart/middlewares';
    
    const myApp = new App({
      port: 3000,
    });
    
    myApp.addMiddlewares([
      cors({ credentials: true }),
      helmet(),
      bodyParser(),
      i18n(myApp.app, {
        directory: join(__dirname, 'locales'),
        locales: ['en', 'fr'],
        modes: ['query', 'subdomain', 'cookie', 'header', 'tld'],
      }),
      handleError(),
      logger(),
      addDefaultBody(),
      compress({}),
      RateLimit.middleware({ interval: { min: 1 }, max: 100 }),
    ]);
        
    // mount a folder with an prefix (all file who extends from `Route` will be add and mount)
    myApp.mountFolder(join(__dirname, 'routes'), '/');
    
    // start the app
    myApp.start();
    
  • Other example who Extends class App

    import { join } from 'path';
    // import the app
    import { App } from 'koa-smart';
    // import middlewares koa-smart give you OR others
    import { 
      i18n,
      bodyParser, 
      compress,
      cors,
      helmet,
      addDefaultBody,
      handleError,
      logger,
      RateLimit,
    } from 'koa-smart/middlewares';
    
    // create an class who extends from App class
    export default class MyApp extends App {
      constructor() {
        super({ port: 3000 });
      }
    
      async start() {
        // add your Middlewares
        super.addMiddlewares([
          cors({ credentials: true }),
          helmet(),
          bodyParser(),
          i18n(this.app, {
            directory: join(__dirname, 'locales'),
            locales: ['en', 'fr'],
            modes: ['query', 'subdomain', 'cookie', 'header', 'tld'],
          }),
          handleError(),
          logger(),
          addDefaultBody(),
          compress({}),
          RateLimit.middleware({ interval: { min: 1 }, max: 100 }),
        ]);
    
        // mount a folder with an prefix (all file who extends from `Route` will be add and mount)
        super.mountFolder(join(__dirname, 'routes'));
        return super.start();
      }
    }
    
    // start the app
    const myApp = new MyApp();
    myApp.start();
    

Add treatment on route

you can add you own treatment and attribute to the route.

In this example we will see how you can manage accesses to your route in 2 steps

  1. Extends Route Class and overload beforeRoute methode
export default class MyRoute extends Route {
  static accesses = {
    public: -1,
    connected: 100,
    admin: GROUPS.ADMIN_ID,
    client: GROUPS.CLIENT_ID,
    // whatever ...
  };

  // overload beforeRoute
  async beforeRoute(ctx, infos, next) {
    // infos.options content all the param give to the route

    if (this.mlCanAccessRoute(ctx, infos.options)) { // test if you can access
      this.throw(StatusCode.forbidden, ctx.state.__('Forbidden access')); 
    }
    // call the super methode
    await super.beforeRoute(ctx, infos, next);
  }

  mlCanAccessRoute(ctx, { accesses }) {
    if (accesses && Array.isArray(accesses)) {
      const { user } = ctx.state;
      return accesses.includes(Route.accesses.public) ||
        (!!user && (
          accesses.includes(Route.accesses.connected) ||
          user.usergroup_id === Route.accesses.admin ||
          accesses.includes(user.usergroup_id)
        ));
    }
    return false;
  }
}

  1. Create an route with access
export default class RouteMyApi extends MyRoute {
  constructor(params) {
    super({ ...params });
  }

  @Route.Get({ accesses: [MyRoute.accesses.public] })
  async publicRoute(ctx) {
    this.sendOk(ctx, ctx.i18n.__('I can be call by any one'));
  }

  @Route.Get({ accesses: [MyRoute.accesses.client] })
  async clientRoute(ctx) {
    this.sendOk(ctx, ctx.i18n.__('I can be call by only client user'));
  }

  @Route.Get({ accesses: [MyRoute.accesses.admin] })
  async adminRoute(ctx) {
    this.sendOk(ctx, ctx.state.__('I can be call by only admin user'));
  }

  @Route.Get({ accesses: [MyRoute.accesses.client, MyRoute.accesses.admin] })
  async adminRoute(ctx) {
    this.sendOk(ctx, ctx.state.__('I can be call by client and admin user'));
  }

  @Route.Get({ accesses: [MyRoute.accesses.connected] })
  async adminRoute(ctx) {
    this.sendOk(ctx, ctx.state.__('I can be call by all connected users'));
  }
}

License

MIT © YSO Corp

Keywords

FAQs

Package last updated on 29 Dec 2017

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc