npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

koa-smart

v4.1.1

Published

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

Downloads

302

Readme

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, 'User not found');
      this.sendOk(ctx, user);
    }

    // post route: http://localhost:3000/users/add
    @Route.Post({
      accesses: [Route.accesses.public],
      bodyType: Types.object().keys({
        email: Types.string().required(), // return an 400 if the body doesn't contain email key
        name: Types.string().uppercase(), // optional parameter
      }),
    })
    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);
    }

  }

Api documentation

Summary

What is in this framework ?

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

Install

npm install --save koa-smart Or use the boilerplate (koa-smart-boilerplate)

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, '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, 'hello');
          }
      
      }
  • Get route http://localhost:3000/my-api/hello

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

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

      @Route.Post({
        bodyType: Types.object().keys({ // body to allow: all other params will be rejected
          email: Types.string().required(), // return an 400 if the body doesn't contain email key
          name: Types.string().uppercase(), // 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, 'hello');
      }
  • Grant accesses

    Koa smart allows grant permission to be handled in a simple and efficient manner.

    Each function passed to accessers will be given the koa context (ctx) as a parameter, and must return a boolean to express whether is grants access to the route or not.

    If at least one of the function given returns true, access to the route will be granted.

      async function isConnected(ctx) {
        // TODO test if the user is connected
        return ctx.state.user;
      }
      async function isUserPremium(ctx) {
        // TODO test if the user is premium
        return ctx.state.user.isPremium;
      }
      async function isAdmin(ctx) {
        // TODO test if the user is a admin
        return ctx.state.user.isAdmin;
      }
    • Of a Class

      @Route.Route({ accesses: [isConnected] })
      class RouteMiddlewares extends Route {
        @Route.Get({})
        async view(ctx, next) {
          console.log('I can be call if the current client is connected');
          this.sendOk(ctx, null, 'OK');
        }
      }
    • Of a specific route

      @Route.Get({})
      async myPublicRoute(ctx, next) {
        console.log('I am a public route, I can be call by any one');
        this.sendOk(ctx, null, 'OK');
      }
      
      @Route.Get({ accesses: [isConnected] })
      async myConnectedRoute(ctx, next) {
        console.log('I can be call if the current client is connected');
        this.sendOk(ctx, null, 'OK');
      }
      
      @Route.Get({ accesses: [isUserPremium, isAdmin] })
      async myPremiumRoute(ctx, next) {
        console.log('I can be call if the current client is connected and premium or admin');
        this.sendOk(ctx, null, 'OK');
      }
  • 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, '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, 'hello');
      }
  • Middlewares

    • Of a 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 {
          @Route.Get({})
          async view(ctx, next) {
            console.log('I will be call after middlewares of class');
            this.sendOk(ctx, null, 'hello');
          }
      }
    • 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, 'hello');
      }

Params checker: See the doc of Types for more information

  • bodyType to check body params

    • quick example

        @Route.Post({ // or Put, Patch
          bodyType: Types.object().keys({
            email: Types.string().regex(/\S+@\S+\.\S+/).required(),
            password: Types.string().min(8).required(),
            address: Types.object().keys({
              country: Types.string().required(),
              street: Types.string().required(),
            }).required(),
          }),
        })
        async user(ctx) {
          // this is the body manage by bodyType
          const bodyParams = this.body(ctx);
      
          // this is the origin body pass
          const originBodyParams = this.body(ctx, true);
        }
  • queryType to check query params

    • quick example

        @Route.Get({
          queryType: Types.object().keys({
            limit: Types.number().integer().required().default(10),
            offset: Types.number().integer().required().default(10),
          }),
        })
        async users(ctx) {
          // this can contain only limit and offset
          const queryParams = this.queryParam(ctx);
      
          // this is the origin queryParams pass
          const originQueryParams = this.queryParam(ctx, true);
        }

Automatic documention generation: See the manual for more information

Get Started (quick-start boilerplate)

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

  • 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({ multipart: true }),
      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({ multipart: true }),
      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({ multipart: true }),
          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();

Upgrade to 4.0.0

4.0.0 upgrades koa2-ratelimit to version 1.0.0

This means koa2-ratelimit does not install sequelize, mongoose or redis anymore. If you use these packages, make sure you install them in your project.

License

MIT © YSO Corp