KoaSmart is a framework based on Koajs2, which allows you to develop RESTful APIs with : Class, Decorator, Params checker
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);
}
}
- What's in this framework ?
- Install
- Router with decorator
- Params checker of POST body
- Automatic documention generation
- Get Started
- Full example
- Add treatment on route
**This framework gives you the tools to use a set of modules: **
-
For routing
koajs 2
as the main, underlying frameworkkcors
is used to handle cross-domain requestskoa2-ratelimit
To limit bruteforce requestskoa-helmet
helps you secure your apikoa-body
to parse request bodieskoa-compress
to compress the responsekoa-i18n
for Internationalization (I18n)
-
moment
Parse, validate, manipulate, and display dates in javascript. -
jsonwebtoken
an implementation of JSON Web Tokens JWT -
💪
@Decorators
to ensure a better project structure
npm install --save koa-smart
Or use the boilerplate (koa-smart-boilerplate)
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 ?
- the
Route
word is removed - 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'); } }
- the
-
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 thekoa context (ctx)
as a parameter, and must return aboolean
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
-
-
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); }
-
-
-
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();
-
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();
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.
MIT © YSO Corp