Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/ysocorp/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
https://github.com/ysocorp/koa-smart
boilerplate class decorators essential framework koa-boilerplate koa2 koajs parameters router
Last synced: 13 days ago
JSON representation
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
- Host: GitHub
- URL: https://github.com/ysocorp/koa-smart
- Owner: ysocorp
- Created: 2017-12-06T10:48:17.000Z (almost 7 years ago)
- Default Branch: develop
- Last Pushed: 2022-03-11T11:04:20.000Z (over 2 years ago)
- Last Synced: 2024-04-25T12:03:01.439Z (7 months ago)
- Topics: boilerplate, class, decorators, essential, framework, koa-boilerplate, koa2, koajs, parameters, router
- Language: TypeScript
- Homepage:
- Size: 757 KB
- Stars: 33
- Watchers: 7
- Forks: 11
- Open Issues: 1
-
Metadata Files:
- Readme: README-V1.md
Awesome Lists containing this project
README
# **KoaSmart** is a framework based on **Koajs2**, which allows you to develop RESTful APIs with : **Class**, **Decorator**, **Params checker**
[![Build Status](https://secure.travis-ci.org/ysocorp/koa-smart.png?branch=master "Test")](http://travis-ci.org/ysocorp/koa-smart)
[![NPM version](http://badge.fury.io/js/koa-smart.png)](https://npmjs.org/package/koa-smart "View this project on NPM")A 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
```sh
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's in this framework ?
* [Install](#install)
* [Router with decorator](#router-with-decorator)
* [Params checker of POST body](#params-checker-of-post-body)
* [Get Started](#get-started)
* [Full example](#full-example)
* [Add treatment on route](#add-treatment-on-route)## What is in this framework ?
**This framework gives you the tools to use a set of modules: **
* **For routing**
* [`koajs 2`](https://github.com/koajs/koa) as the main, underlying framework
* [`kcors`](https://www.npmjs.com/package/kcors) is used to handle cross-domain requests
* [`koa2-ratelimit`](https://github.com/ysocorp/koa2-ratelimit) To limit bruteforce requests
* [`koa-helmet`](https://www.npmjs.com/package/koa-helmet) helps you secure your api
* [`koa-bodyparser`](https://github.com/koajs/bodyparser) to parse request bodies
* [`koa-compress`](https://github.com/koajs/compress) to compress the response
* [`koa-i18n`](https://github.com/koa-modules/i18n) for Internationalization (I18n)
* [`@Decorators`](https://babeljs.io/docs/plugins/transform-decorators/) to ensure a better project structure
* [`moment`](https://github.com/moment/moment) Parse, validate, manipulate, and display dates in javascript.
* [`lodash`](https://github.com/lodash/lodash) A modern JavaScript utility library delivering modularity, performance, & extras
* [`jsonwebtoken`](https://github.com/auth0/node-jsonwebtoken) an implementation of [JSON Web Tokens JWT](https://tools.ietf.org/html/rfc7519)the full documentation for this module can be found [here](https://ysocorp.github.io/koa-smart/)
## 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```sh
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
```sh
@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
```sh
@Route.Get({})
async hello(ctx) {
this.sendOk(ctx, null, ctx.state.__('hello'));
}
```
* **Change path** http://localhost:3000/my-api/myroute/15```sh
@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
```sh
@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
```sh
@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
```sh
@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`](https://github.com/ysocorp/koa2-ratelimit) module
* **Configure**
```sh
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
```sh
@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
```sh
// 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 a Class**
```sh
@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**
```sh
@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```sh
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` to be executed on the field one by one in order to validate / transform it
* Eg:```sh
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:**```sh
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](https://github.com/ysocorp/koa-smart-light-example))
in order to get started quickly, look at [this boilerplate](https://github.com/ysocorp/koa-smart-light-example), or follow the instructions below:
* import the app and your middlewares
```sh
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
```sh
const myApp = new App({
port: 3000,
});
```* add your middlewares
```sh
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)```sh
myApp.mountFolder(join(__dirname, 'routes'), '/');
```* Start your app
```sh
myApp.start();
```### Full example
* Basic one
```sh
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
```sh
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
```sh
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 routeif (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;
}
}```
2. Create a route with access
```sh
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](http://ysocorp.com/)