• Stars
    star
    101
  • Rank 326,953 (Top 7 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created about 7 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Awilix helpers/middleware for Express

awilix-express

npm version Dependency Status devDependency Status Build Status Coverage Status Typings Included

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({
  // Scoped lifetime = new instance per request
  // Imagine the TodosService needs a `user`.
  // class TodosService { constructor({ user }) { } }
  todosService: asClass(TodosService).scoped()
})

// Add the middleware, passing it your Awilix container.
// This will attach a scoped container on the context.
app.use(scopePerRequest(container))

// Now you can add request-specific data to the scope.
app.use((req, res, next) => {
  req.container.register({
    user: asValue(req.user) // from some authentication middleware...
  })

  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)

// Creates middleware that will invoke `makeAPI`
// for each request, giving you a scoped instance.
router.get('/todos', api('find'))

Awesome Usage

As of [email protected], 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' // or `awilix-router-core`

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') // Prefix all endpoints with `/todo`
  .before([authenticate()]) // run authentication for all endpoints
  .get('/:id', 'getTodo') // Maps `GET /todos/:id` to the `getTodo` function on the returned object from `API`
  .post('', 'createTodo', {
    // Maps `POST /todos` to the `createTodo` function on the returned object from `API`
    before: [bodyParser()] // Runs the bodyParser just for this endpoint
  })

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' // or `awilix-router-core`

@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))
// Loads all controllers in the `routes` folder
// relative to the current working directory.
// This is a glob pattern.
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:

// A totally framework-independent piece of application code.
// Nothing here is remotely associated with HTTP, Express or anything.
class TodosService {
  constructor({ currentUser, db }) {
    // We depend on the current user!
    this.currentUser = currentUser
    this.db = db
  }

  getTodos() {
    // use your imagination ;)
    return this.db('todos').where('user', this.currentUser.id)
  }
}

// Here's a Express API that calls the service
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) => {
  // We need a new instance for each request,
  // else the currentUser trick wont work.
  const api = new TodoAPI({
    todosService: new TodosService({
      db,
      // current user is request specific.
      currentUser: req.user
    })
  })

  // invoke the method.
  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()

// The `TodosService` lives in services/TodosService
container.loadModules(['services/*.js'], {
  // we want `TodosService` to be registered as `todosService`.
  formatName: 'camelCase',
  resolverOptions: {
    // We want instances to be scoped to the Express request.
    // We need to set that up.
    lifetime: Lifetime.SCOPED
  }
})

// imagination is a wonderful thing.
app.use(someAuthenticationMethod())

// We need a middleware to create a scope per request.
// Hint: that's the scopePerRequest middleware in `awilix-express` ;)
app.use((req, res, next) => {
  // We want a new scope for each request!
  req.container = container.createScope()
  // The `TodosService` needs `currentUser`
  req.container.register({
    currentUser: asValue(req.user) // from auth middleware... IMAGINATION!! :D
  })
  return next()
})

Okay! Let's try setting up that API again!

export default function(router) {
  router.get('/todos', (req, res) => {
    // We have our scope available!
    const api = new TodoAPI(req.container.cradle) // Awilix magic!
    return api.getTodos(req, res)
  })
}

A lot cleaner, but we can make this even shorter!

export default function(router) {
  // Just invoke `api` with the method name and
  // you've got yourself a middleware that instantiates
  // the API and calls the method.
  const api = methodName => {
    // create our handler
    return function(req, res) {
      const controller = new TodoAPI(req.container.cradle)
      return controller[method](req, res)
    }
  }

  // adding more routes is way easier!
  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()

// The `TodosService` lives in services/TodosService
container.loadModules(
  [
    ['services/*.js', Lifetime.SCOPED] // shortcut to make all services scoped
  ],
  {
    // we want `TodosService` to be registered as `todosService`.
    formatName: 'camelCase'
  }
)

// imagination is a wonderful thing.
app.use(someAuthenticationMethod())

// Woah!
app.use(scopePerRequest(container))
app.use((req, res, next) => {
  // We still want to register the user!
  // req.container is a scope!
  req.container.register({
    currentUser: asValue(req.user) // from auth middleware... IMAGINATION!! :D
  })
})

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

More Repositories

1

awilix

Extremely powerful Inversion of Control (IoC) container for Node.JS
TypeScript
2,999
star
2

typesync

Install missing TypeScript typings for dependencies in your package.json.
TypeScript
1,390
star
3

koa-es7-boilerplate

A boilerplate for writing Koa 2 apps in ES7 with Babel.
JavaScript
274
star
4

mobx-task

Makes async function state management in MobX fun.
TypeScript
237
star
5

messageformat.net

ICU MessageFormat implementation for .NET.
C#
148
star
6

awilix-koa

Awilix helpers/middleware for Koa 2
TypeScript
129
star
7

yenv

Environment management for Node using YAML.
JavaScript
104
star
8

koa-respond

Koa middleware that adds useful methods to the context.
JavaScript
102
star
9

libx

Collection + Model infrastructure for MobX applications.
TypeScript
99
star
10

httpclientgoodies.net

Useful utilities for the .NET HttpClient.
C#
62
star
11

snicket

Stream Store based on Postgres for Node.JS
TypeScript
39
star
12

screengun

A simple screen recorder for Windows based on ffmpeg
C#
39
star
13

fejl

Error-making utility for Node apps.
TypeScript
35
star
14

logpipe-server

The Logpipe server.
JavaScript
30
star
15

awilix-router-core

Reusable building blocks for writing Awilix router adapters for HTTP frameworks.
TypeScript
25
star
16

validx

Validation library for MobX.
TypeScript
23
star
17

keyblade

Fail fast when accessing undefined properties on objects.
JavaScript
14
star
18

npm-module-boilerplate

A boilerplate for authoring npm modules, with tests and linting.
JavaScript
10
star
19

skadi

A simple object validator/sanitizer based on `is-my-json-valid`.
JavaScript
9
star
20

deltio

A Google Cloud Pub/Sub emulator alternative, written in Rust.
Rust
8
star
21

dration

Duration utilities for JavaScript using primitive types.
TypeScript
6
star
22

icebug

A wrapper around node-inspector and nodemon.
JavaScript
4
star
23

jQuery-Validator.Knockout

KnockoutJS bindings for my jQuery-Validator.
JavaScript
4
star
24

bristol-sentry

Sentry integration for the Bristol logger
JavaScript
3
star
25

smid

Catches errors and returns them. Useful for unit testing.
TypeScript
3
star
26

baconpi-web

BaconPi Web Frontend + Backend
JavaScript
3
star
27

ts-module-boilerplate

TypeScript module boilerplate for Node packages
TypeScript
3
star
28

posish

A tool that makes counting string lines, columns and indexes awesome. Useful for writing parser tests.
JavaScript
2
star
29

total-rename

Utility to replace strings in files and paths.
Go
2
star
30

purge

Windows utility for deleting a directory tree with long paths.
C#
1
star
31

chilli

Rename files to sort-friendly numbers
F#
1
star
32

jQuery-Validator

A formless validation plugin for all kinds of validation - even your own!
JavaScript
1
star