Awesome
awilix-router-core
This package is intended for use with HTTP libraries that want to configure routes using ESNext decorators or a builder pattern.
Table of Contents
Install
With npm
:
npm install awilix-router-core
Or with yarn
yarn add awilix-router-core
Example
The end-user of the routing library will be able to use decorators or a builder pattern to declaratively set up their routes, middleware and methods.
Note: in the examples below, an ES6 default
export is used, but named exports and multiple exports
per file are supported.
With decorators
// You may re-export these as well.
import { route, before, GET, verbs, HttpVerbs } from 'awilix-router-core'
import bodyParser from 'your-framework-body-parser'
import authenticate from 'your-framework-authentication'
@before(bodyParser())
@route('/news')
export default class NewsController {
constructor({ service }) {
this.service = service
}
@GET()
async find(ctx) {
ctx.body = await this.service.doSomethingAsync()
}
@route('/:id')
@GET()
async get(ctx) {
ctx.body = await this.service.getNewsOrWhateverAsync(ctx.params.id)
}
@route('(/:id)')
@verbs([HttpVerbs.POST, HttpVerbs.PUT])
@before(authenticate())
async save(ctx) {
ctx.body = await this.service.saveNews(ctx.params.id, ctx.request.body)
}
}
With builder pattern
// You may re-export these as well.
import { createController } from 'awilix-router-core'
import bodyParser from 'your-framework-body-parser'
import authenticate from 'your-framework-authentication'
// Can use a factory function or a class.
const api = ({ service }) => ({
find: async () => (ctx.body = await service.doSomethingAsync()),
get: async (ctx) =>
(ctx.body = await service.getNewsOrWhateverAsync(ctx.params.id)),
save: async (ctx) =>
(ctx.body = await service.saveNews(ctx.params.id, ctx.request.body)),
})
export default createController(api)
.before(bodyParser())
.prefix('/news')
.get('', 'find') // <- "find" is the method on the result from `api`
.get('/:id', 'get') // <- "get" is the method on the result from `api`
.verbs([HttpVerbs.POST, HttpVerbs.PUT], '/:id', 'save', {
// "save" is the method on the result from `api`
before: [authenticate()],
})
For framework adapter authors
The framework adapter will use the tools provided by this package to extract routing config from decorated classes and register it in the router of choice.
Check out the awilix-koa
reference implementation, as well as the API docs here.
API
As mentioned earlier, this package exposes the user-facing route declaration API, as well as utilities needed for framework adapter authors.
Route Declaration
There are 2 flavors of route declaration: builder and ESNext decorators.
Builder
The builder API's public top level exports are:
import { createController, HttpVerbs } from 'awilix-router-core'
createController(targetClassOrFunction)
Creates a controller that will invoke methods on an instance of the specified targetClassOrFunction
.
The controller exposes the following builder methods:
.get|post|put|patch|delete|head|options|connect|all(path, method, opts)
: shorthands for.verbs([HttpVerbs.POST], ...)
- seeHttpVerbs
for possible values..verbs(verbs, path, method, opts)
: registers a path mapping for the specified controller method..prefix(path)
: registers a prefix for the controller. Calling this multiple times adds multiple prefix options..before(middlewares)
: registers one or more middlewares that runs before any of the routes are processed..after(middlewares)
: registers one or more middlewares that runs after the routes are processed.
The optional opts
object passed to .verbs
can have the following properties:
before
: one or more middleware that runs before the route handler.after
: one or more middleware that runs after the route handler.
Note: all builder methods returns a new builder - this means the builder is immutable! This allows you to have a common builder setup that you can reuse for multiple controllers.
Decorators
If you have enabled decorator support in your transpiler, you can use the decorator API.
The decorator API exports are:
import {
route,
before,
after,
verbs,
HttpVerbs,
// The following are just shortcuts for `verbs([HttpVerbs..])`
GET,
HEAD,
POST,
PUT,
DELETE,
CONNECT,
OPTIONS,
PATCH,
ALL,
} from 'awilix-router-core'
route(path)
Class-level: adds a prefix to all routes in this controller.
Method-level: adds a route for the decorated method in the controller.
Has no effect if no verbs
are configured.
Example:
@route('/todos')
class Controller {
// GET /todos
// POST /todos
@GET()
@POST()
method1() {}
// PATCH /todos/:id
@route('/:id')
@PATCH()
method2() {}
}
before(middlewares)
and after(middlewares)
Class-level: adds middleware to run before/after the routes are processed.
Method-level: adds middleware to run before/after the decorated method is processed.
Example:
@before([bodyParser()])
class Controller {
@before([authenticate()])
@after([compress()])
method() {}
}
verbs(httpVerbs)
Class-level: not allowed.
Method-level: adds HTTP verbs that the route will match.
Has no effect if no route
s are configured.
Example:
@verbs([HttpVerbs.GET, HttpVerbs.POST])
method() {}
Verb shorthands
GET
, POST
, etc.
Example:
@route('/todos')
class Controller {
// GET /todos
// POST /todos
@GET()
@POST()
method1() {}
// PATCH /todos/:id
@route('/:id')
@PATCH()
method2() {}
}
Extracting route config
This section is for framework adapter authors. Please see awilix-koa for a reference implementation. If you need any help, please feel free to reach out!
The primary functions needed for this are getStateAndTarget
, rollUpState
, and findControllers
.
NOTE: when referring to "state-target tuple", it means an object containing
state
andtarget
properties, wheretarget
is the class/function to build up (usingcontainer.build
) in order to get an object to call methods on.
import {
getStateAndTarget,
rollUpState,
findControllers,
} from 'awilix-router-core'
getStateAndTarget(functionOrClassOrController)
Given a controller (either from createController
or a decorated class), returns a state-target tuple.
rollUpState(state)
This will return a map where the key is the controller method name and the value is the routing config to set up for that method, with root paths + middleware stacks pre-merged.
findControllers(pattern, opts)
Using fast-glob
, loads controllers from matched files.
opts
is a merge of glob options to pass into glob for pattern matching and the esModules
property which, when set to true
returns a Promise
that loads controller modules via dynamic import()
(for async/ ES modules usage).
Returns an array (or a Promise
that resolves to an array) of state-target tuples.
Author
Jeff Hansen — @Jeffijoe