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

Requires Node v6 or above

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.

: creates a scope per request. controller(decoratedClassOrController) : registers routes and delegates to Express.Router.

: registers routes and delegates to Express.Router. loadControllers(pattern, opts) : loads files matching a glob pattern and registers their exports as controllers.

: loads files matching a glob pattern and registers their exports as controllers. makeInvoker(functionOrClass, opts)(methodName) : using isClass , calls either makeFunctionInvoker or makeClassInvoker .

: using , calls either or . makeClassInvoker(Class, opts)(methodName) : resolves & calls methodName on the resolved instance, passing it req , res and next .

: resolves & calls on the resolved instance, passing it , and . makeFunctionInvoker(function, opts)(methodName) : resolves & calls methodName on the resolved instance, passing it req , res and next .

: resolves & calls on the resolved instance, passing it , and . makeResolverInvoker(resolver, opts) : used by the other invokers, exported for convenience.

: 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

: Runs tests once npm run lint : Lints + formats the code once

: Lints + formats the code once npm run cover : Runs code coverage using istanbul

Author

Talysson Oliveira Cassiano - @talyssonoc