awilix-express

Awilix helpers, router and scope-instantiating middleware for Express. 🐨
✨ NEW IN V1: first-class router support with auto-loading! 🚀
Table of Contents
Installation
npm install --save awilix-express
Basic Usage
Add the middleware to your Express app.
const { asClass, asValue, createContainer } = require('awilix')
const { scopePerRequest } = require('awilix-express')
const container = createContainer()
container.register({
todosService: asClass(TodosService).scoped(),
})
app.use(scopePerRequest(container))
app.use((req, res, next) => {
req.container.register({
user: asValue(req.user),
})
return next()
})
Then in your route handlers...
const { makeInvoker } = require('awilix-express')
function makeAPI({ todosService }) {
return {
find: (req, res) => {
return todosService.find().then((result) => {
res.send(result)
})
},
}
}
const api = makeInvoker(makeAPI)
router.get('/todos', api('find'))
Awesome Usage
As of awilix-express@1.0.0
, we ship with Express.Router
bindings for awilix-router-core
!
This is cool because now your routing setup can be streamlined with first-class Awilix support!
The Awilix-based router comes in 2 flavors: a builder and ESNext decorators.
routes/todos-api.js
- demos the builder pattern
import bodyParser from 'body-parser'
import { authenticate } from './your-auth-middleware'
import { createController } from 'awilix-express'
const API = ({ todoService }) => ({
getTodo: async (req, res) => {
res.send(await todoService.get(req.params.id))
},
createTodo: async (req, res) => {
res.send(await todoService.create(req.body))
},
})
export default createController(API)
.prefix('/todos')
.before([authenticate()])
.get('/:id', 'getTodo')
.post('', 'createTodo', {
before: [bodyParser()],
})
routes/users-api.js
- demos the decorator pattern
import bodyParser from 'body-parser'
import { authenticate } from './your-auth-middleware'
import { route, GET, POST, before } from 'awilix-express'
@route('/users')
export default class UserAPI {
constructor({ userService }) {
this.userService = userService
}
@route('/:id')
@GET()
@before([authenticate()])
async getUser(req, res) {
res.send(await this.userService.get(req.params.id))
}
@POST()
@before([bodyParser()])
async createUser(req, res) {
res.send(await this.userService.create(req.body))
}
}
server.js
import Express from 'express'
import { asClass, createContainer } from 'awilix'
import { loadControllers, scopePerRequest } from 'awilix-express'
const app = Express()
const container = createContainer().register({
userService: asClass(),
todoService: asClass(),
})
app.use(scopePerRequest(container))
app.use(loadControllers('routes/*.js', { cwd: __dirname }))
app.listen(3000)
Please see the awilix-router-core
docs for information about the full API.
Why do I need it?
You can certainly use Awilix with Express without this library, but follow along and you might see why it's useful.
Imagine this simple imaginary Todos app, written in ES6:
class TodosService {
constructor({ currentUser, db }) {
this.currentUser = currentUser
this.db = db
}
getTodos() {
return this.db('todos').where('user', this.currentUser.id)
}
}
class TodoAPI {
constructor({ todosService }) {
this.todosService = todosService
}
getTodos(req, res) {
return this.todosService.getTodos().then((todos) => res.send(todos))
}
}
So the problem with the above is that the TodosService
needs a currentUser
for it to function. Let's first try solving this manually, and then with awilix-express
.
Manual
This is how you would have to do it without Awilix at all.
import db from './db'
router.get('/todos', (req, res) => {
const api = new TodoAPI({
todosService: new TodosService({
db,
currentUser: req.user,
}),
})
return api.getTodos(req, res)
})
Let's do this with Awilix instead. We'll need a bit of setup code.
import { asValue, createContainer, Lifetime } from 'awilix'
const container = createContainer()
container.loadModules(['services/*.js'], {
formatName: 'camelCase',
resolverOptions: {
lifetime: Lifetime.SCOPED,
},
})
app.use(someAuthenticationMethod())
app.use((req, res, next) => {
req.container = container.createScope()
req.container.register({
currentUser: asValue(req.user),
})
return next()
})
Okay! Let's try setting up that API again!
export default function (router) {
router.get('/todos', (req, res) => {
const api = new TodoAPI(req.container.cradle)
return api.getTodos(req, res)
})
}
A lot cleaner, but we can make this even shorter!
export default function (router) {
const api = (methodName) => {
return function (req, res) {
const controller = new TodoAPI(req.container.cradle)
return controller[method](req, res)
}
}
router.get('/todos', api('getTodos'))
}
Using awilix-express
In our route handler, do the following:
import { makeInvoker } from 'awilix-express'
export default function (router) {
const api = makeInvoker(TodoAPI)
router.get('/todos', api('getTodos'))
}
And in your express application setup:
import { asValue, createContainer, Lifetime } from 'awilix'
import { scopePerRequest } from 'awilix-express'
const container = createContainer()
container.loadModules(
[
['services/*.js', Lifetime.SCOPED],
],
{
formatName: 'camelCase',
},
)
app.use(someAuthenticationMethod())
app.use(scopePerRequest(container))
app.use((req, res, next) => {
req.container.register({
currentUser: asValue(req.user),
})
})
Now that is way simpler!
import { makeInvoker } from 'awilix-express'
function makeTodoAPI({ todosService }) {
return {
getTodos: (req, res) => {
return todosService.getTodos().then((todos) => res.send(todos))
},
}
}
export default function (router) {
const api = makeInvoker(makeTodoAPI)
router.get('/api/todos', api('getTodos'))
}
That concludes the tutorial! Hope you find it useful, I know I have.
API
The package exports everything from awilix-router-core
as well as the following Express middleware factories:
scopePerRequest(container)
: creates a scope per request.
controller(decoratedClassOrController)
: registers routes and delegates to Express.Router.
loadControllers(pattern, opts)
: loads files matching a glob pattern and registers their exports as controllers.
makeInvoker(functionOrClass, opts)(methodName)
: using isClass
, calls either makeFunctionInvoker
or makeClassInvoker
.
makeClassInvoker(Class, opts)(methodName)
: resolves & calls methodName
on the resolved instance, passing it req
, res
and next
.
makeFunctionInvoker(function, opts)(methodName)
: resolves & calls methodName
on the resolved instance, passing it req
, res
and next
.
makeResolverInvoker(resolver, opts)
: used by the other invokers, exported for convenience.
inject(middlewareFactory)
: resolves the middleware per request.
app.use(
inject(({ userService }) => (req, res, next) => {
}),
)
Contributing
npm run
scripts
npm run test
: Runs tests once
npm run lint
: Lints + formats the code once
npm run cover
: Runs code coverage using istanbul
Author