Giter Club home page Giter Club logo

awilix-express's Introduction

awilix-express

npm version CI 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

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

awilix-express's People

Contributors

blove avatar jeffijoe avatar jhoscar1 avatar talyssonoc avatar whefter avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

awilix-express's Issues

Add TypeScript Definition file (index.d.ts)

This looks like a great solution for dependency injection in Express! It would be great if there was a TypeScript definition file (index.d.ts) so that the container property on the Request interface existed. Otherwise TS developers get an error: Property 'container' does not exist on type 'Request'.

Awilix 5.x is out

It would be great if awilix 5 (5.0.1 right now) could be supported

Accessing controller instance on before/after middlewares

I'm setting up a controller with awilix-express, it works well, but I have some doubts.

In this example below, is it possible to setup a middleware on before that access the instance TodoController?

const { createController } = require('awilix-express')

class TodoController {
  constructor ({ todosService, cacheService }) {
    this.todos = todoService
    this.cacheMiddleware = cacheService
  }
  
  getAll(req, res) => {
    return res.send(this.todosService.getTodos())
  }
  
}

module.exports = createController(TodoController)
  .get('/todos', 'getAll', {
    before: [ this.cacheMiddleware() ] // I know "this" doesn't work here, but I need to access cacheMiddleware, How?
  })

Is there a way to do that? Maybe changing approach and not using a class? Can you show an example please?
Thanks!

Cannot call a class as a function

Hi,

I tried to use the decorator way, I got this error when I call a route

Called GET http://localhost:4000/users
TypeError: Cannot call a class as a function
    at _classCallCheck (/Users/guillaume/Developer/Javascript/PiinsBackEnd/node_modules/@babel/runtime/helpers/classCallCheck.js:3:11)
    at UserApi (/Users/guillaume/Developer/Javascript/PiinsBackEnd/routes/users.js:8:26)
    at Layer.handle [as handle_request] (/Users/guillaume/Developer/Javascript/PiinsBackEnd/node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (/Users/guillaume/Developer/Javascript/PiinsBackEnd/node_modules/express/lib/router/index.js:317:13)
    at /Users/guillaume/Developer/Javascript/PiinsBackEnd/node_modules/express/lib/router/index.js:284:7
    at Function.process_params (/Users/guillaume/Developer/Javascript/PiinsBackEnd/node_modules/express/lib/router/index.js:335:12)
    at Immediate.next (/Users/guillaume/Developer/Javascript/PiinsBackEnd/node_modules/express/lib/router/index.js:275:10)
    at Immediate._onImmediate (/Users/guillaume/Developer/Javascript/PiinsBackEnd/node_modules/express/lib/router/index.js:635:15)
    at runCallback (timers.js:764:11)
    at tryOnImmediate (timers.js:734:5)
    at processImmediate (timers.js:716:5)

routes/users.js

import bodyParser from 'body-parser';
import { route, GET, PUT, before } from 'awilix-express';
import User from '../models/User';
import passport from 'passport';

@route('/users')
export default class UserApi {
    constructor({logger}){
        this.logger = logger;
    }

    @GET()
    @before([passport.authenticate('jwt')])
    async getUsers(req, res) {
        try{
            const users = await User.find();
            res.json(users);
        }catch(err){
            this.logger.log(err);
            res.json({status: 500, message: 'an error occured'});
        }
    }

    @route('/:id')
    @GET()
    @before([passport.authenticate('jwt')])
    async getUser(req, res) {
        try{
            const user = await User.findById(req.params.id);
            res.json(user);
        }catch(err){
            this.logger.log(err);
            res.json({status: 500, message: 'an error occured'});
        }
    }

    @PUT()
    @before([bodyParser.json()])
    async update(req, res){
        const user = await User.findById(req.user.id);
        
        if (!user) {
            return next(new Error('Could not load document'));
        }

        user['pseudo'] = req.body.pseudo;
        user.firstName = req.body.firstName;
        user.lastName = req.body.lastName;
        user.email = req.body.email;
        user.introduction = req.body.introduction;

        try{
            await user.save();
            return res.json({ status: 200, user });
        }catch(err){
            this.logger.log(err);
            return res.status(400).json({ status: 400, error: "update failed" });
        }
    }
    
    @route('/me')
    @GET()
    @before([passport.authenticate('jwt')])
    me(req, res){
        res.json(req.user);
    }
   

    @route('/protected')
    @GET()
    @before([passport.authenticate('jwt')])
    protectedRoute(req, res){
        res.json(req.user);
    }
}

server.js

import express from 'express';
import cors from 'cors';
import bodyParser from 'body-parser';
import mongoose from 'mongoose';
import users from './routes/users';
import upload from './routes/upload';
import passport from './middlewares/passport';
import expressWS from 'express-ws';
import {scopePerRequest, loadControllers} from  'awilix-express';
import {configureContainer} from './lib/container';

import test from './ws-routes/test';

const app = express();

expressWS(app);

const router = express.Router();

const container = configureContainer();

app.use(scopePerRequest(container));
app.use('/static', express.static('public'));
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(passport.initialize());

const connection = mongoose.connection;

connection.once('open', () => {
    console.log('MongoDB database connection established successfully!');
});

app.use('/', router);
app.use(users);
app.use(upload);

app.use(loadControllers('routes/*.js', { cwd: __dirname }));

test(app);

const port = process.env.PORT || 4000;

app.listen(port, () => console.log(`Express server running on port ${port}`));

lib/container.js

import { createContainer, Lifetime, InjectionMode, asValue, asFunction } from 'awilix'
import { logger } from './logger'
import db from './mongoose'
/**
 * Using Awilix, the following files and folders (glob patterns)
 * will be loaded.
 */
const modulesToLoad = [
  // Services should be scoped to the request.
  // This means that each request gets a separate instance
  // of a service.
  ['services/*.js', Lifetime.SCOPED],
  // Stores will be singleton (1 instance per process).
  // This is just for demo purposes, you can do whatever you want.
  ['stores/*.js', Lifetime.SINGLETON]
]

/**
 * Configures a new container.
 *
 * @return {Object} The container.
 */
export function configureContainer () {
  const opts = {
    // Classic means Awilix will look at function parameter
    // names rather than passing a Proxy.
    injectionMode: InjectionMode.CLASSIC
  }
  return createContainer(opts)
    .loadModules(modulesToLoad, {
      // `modulesToLoad` paths should be relative
      // to this file's parent directory.
      cwd: `${__dirname}/..`,
      // Example: registers `services/todo-service.js` as `todoService`
      formatName: 'camelCase'
    })
    .register({
      // Our logger is already constructed,
      // so provide it as-is to anyone who wants it.
      logger: asValue(logger),
      db:
      // Whenever we first request a `mongoose`, create a connection.
      asFunction(
        () => db
      )
        // Make sure we only have 1 connection per process.
        // (the connection is reused)
        .singleton()
        // Whenever we use `container.dispose()`, close the connection.
        .disposer((conn) => conn.close())
    })
}

.babelrc

{
    "presets": [
        "@babel/preset-env"
    ],
    "plugins": [
        ["@babel/transform-runtime"],
        ["@babel/plugin-proposal-decorators", {legacy: true}],
        ["@babel/plugin-proposal-object-rest-spread"]
    ]
}

Googled the error but got no result, so I maybe I did something wrong.

Cannot convert a Symbol value to a string

I am using awilix-express 0.11.0, awilix 2.8.4 in a Typescript project .
I have a class:

export default class SmsController {
smsService: ISms;
logger: any;

constructor(opts) {
console.log(opts);
this.smsService = opts.smsService;
this.logger = opts.logger;
}

sendCaptcha(req: express.Request, res: express.Response): void {
this.logger.debug(hello sendCaptcha api ${process.pid});
res.json({ msg: 'ok' });
}
}

and in a express router file, I use it like this:

import * as express from 'express';
import Ctrl from '../controllers/sms';
import { SmsService } from '../services/sms';
const { makeClassInvoker } = require('awilix-express');

const router = express.Router();

const api = makeClassInvoker(Ctrl);

router.get('/captcha', api('sendCaptcha'));

export default router;

please note the console.log(opts) in SmsController's constructor, it will trigger an exception which tells:
TypeError: Cannot convert a Symbol value to a string
If I comment this line, everything is ok, I think this is a bug in makeClassInvoker.
Thanks

param decorator

Not a real issue.
Untested but should work. This is a method decorator to put inside your controller, that registers a method as a before middleware that will execute everytime a certain param is present

import { makeInvoker, before } from "awilix-express"

/**
 * Adds a root-level before-middleware that will only be executed if
 * exists a param with the name passed to the function
 */
export function param(paramName:string){
    return function routerDecorator(target : any, name : string ){
        let invoker = makeInvoker(target.constructor)(name)
        function paramMiddleware(req, res, next){
            let param = req.params[paramName]
            param != null ?
                invoker(req, res, next) :
                next()
        }
        before(paramMiddleware)(target)
    }
}

Usage:

class PostController {
  postService : PostService
  constructor({ postService }){
     this.postService = postService
  }

  @param('postId')
   async loadPost(req, res, next) {
      let postId = req.params.postId
      let post = await this.postService.get(postId)
      if(!post){
        throw new NotFoundError()
      }
      req['post'] = post
      next()
   }

  @route('/:postId')
  @GET()
  async getPost(req, res){
    let post : Post = req['post'] // <- should be present otherwise the `loadPost` method would have thrown
    res.json(post)
  }
}

I leave it here in case it is useful for someone

Scoped logger per request?

Is possible to have a scoped logger in a submodule used in the controller?

controller(scoped) => moduleA => moduleB.

I want to use a logger on moduleB scoped to the request (with traceId, correlation, etc). Is it possible? Thanks

Resloving dependencies inside @before

As mentioned in the doc i have to import the middleware... Well is it possible to inject the middleware as follows after registering it to the DI container:

  //inside route handler
  @route('/')
  @GET()
  @before([{ middleware }])
  async findMe(req, res) {

Furthermore even if i import the middleware and pass it to @before, inside the middleware function no dependency is resolved.
middleware.js:

export default function middleware({ tokenService }) { 
  //tokenService is unedefined by the time @before invokes the middleware (when receiving a request)
  //while in my route handler it is resolved as expected
}

How do i deal with middleware which has dependencies if none of the above works as i would expect it ? Is this intended ?

Integrate with awilix-router-core

Hey @talyssonoc — I've been working on a routing abstraction to make routing + dependency resolution super streamlined. The core package is awilix-router-core which does not cater to any specific HTTP framework.

Here's an example usage and reference implementation I did for the awilix-koa package.

All the builder + decorator stuff resides in awilix-router-core, all you need to do is register the result with app.get|post|... as seen in the reference implementation.

Would you be willing to work on this? If you need any help or if something could be more clear, please let me know.

Thanks again for maintaining awilix-express! 😄

Awilix 7.x

Hi there, congrats with the new major on awilix!

Would love to upgrade awilix with awilix-express etc. Thanks in advance for your awesome work 🥳

[question] "@before" middleware with injected dependencies

Hello.

I have no idea if this is possible or not, but I can't seem to find a way to make this work.
Let's say I have this class:

export class AuthorizationMiddlewares {

    tokenManager: TokenManager

    constructor({ tokenManager }) {
        this.tokenManager = tokenManager
    }

    async authorize(req, res, next) {
        try {
            const token = await this.tokenManager.getByAccessToken(this.getAuthorizationToken(req))
            await this.tokenManager.validate(token.accessToken)
            req.token = token
            next()
        }catch(error) {
            next(error)
        }
    }
    
    getAuthorizationToken(req): string {
      // blabla
      return "blabla"
    }
}

and I want to use it as a middleware, the question is how?
Inside the route class, in @before, I cannot use the injected AuthorizationMiddlewares class, as I cannot access the route's instance variables. I read something about @inject but I am not sure if that's the way to go.

My route class looks like this:

@route('/users')
export default class UserAPI {

    authorizationMiddlewares: AuthorizationMiddlewares

    constructor({ authorizationMiddlewares }) {
        this.authorizationMiddlewares = authorizationMiddlewares
    }

    @route("/test")
    @before(this.authorizationMiddleware.authorize) // this is not possible
    @GET()
    async test(req, res) {
        try {
            res.json({ test: true })           
        }catch(error) {
            next(error)
        }
        const role: any = await Role.findByName("user")
        res.json(role)
    }
}

I am aware that I can import AuthorizationMiddlewares directly, but that won't let me inject the token manager class.

Please help :)

build: expected targetOrResolver to be a function or class, but got '...'

Hi,

I'm trying to inject my middleware function as per your example, https://github.com/talyssonoc/node-api-boilerplate/blob/adce807fdd36ac86a44965d0327ec06b9e803225/src/interfaces/http/user/UsersController.js

But I get this error message": "build: expected targetOrResolver to be a function or class, but got. ....",

This is how I registered it

// Operations
container.register({
  getAllEmplois: asClass(GetAllEmplois),
  getEmploi: asClass(GetEmploi),
});

And in my controller:

get router() {
    const router = Router();

    router.get('/', inject('getAllEmplois'), this.all);
    router.get('/:id', inject('getEmploi'), this.one);

    return router;
  }

Am I missing something?

Thank you

Injecting dependencies per route instead of when creating controller

Hi! Love the work you guys have done. Although, I'm having a problem related to registrering certain tokens through middlewares, and then injecting dependencies dependent of these tokens within my controllers. I've looked through awilix and awilix-express documentation and issues, yet I can't find any help regarding the issue I'm facing.

To give you an example, here's my controller:

const InventoryController = {
	get router() {
		const public = Router();
		const controller = makeInvoker(this.makeController);

		public.patch('/:type/route1', controller('function1'));
                public.patch('/route2', controller('function2'));
	
		return { public };
	},

	makeController({ addToSiteInventory, getInventory }) {
		return {
                          function1: async (req, res) => {
				...

				try {
					const addedItems = await addToSiteInventory.execute(...);
					res
						.status(Status.CREATED)
						.json({ status: 'success', data: addedItems });
				} catch (error) {
					console.log('Error', error);
				}
			},

			function2: async (req, res) => {
				...

				try {
					const inventory = await getInventory.execute(...);

					res
						.status(Status.OK)
						.json({ status: 'success', data: inventory });
				} catch (error) {
					console.log('Error', error);
				}
			},
		};
	}
};

module.exports = InventoryController;

Now, I have a couple of middlewares which uses the request's scope to register tokens. In one of my middlewares I register a token called 'type'. Which in this simplified example is a route parameter.

Okay. So the problem is that my usecases, the injected dependencies in my controller; addToSiteInventory and getInventory either rely themselves on 'type' or not. getInventory is a usecase that injects 'type'.

However, when I send a request to route2 I get something along the lines of "Can't inject getInventory, 'token' isn't registered". This is due to my middleware not registering 'token', and it's not suppose to for route2.

My question is, is there anyway to have route-specific injections? In that case I can inject each usecase for each route. Instead of injecting all usecases in my controller which leads to the aforementioned problem. I've tried
public.patch('/:type/route1', inject('getInventory') controller('function1'));
But with that attempt I get something along the lines of "Can't build of undefined".

unhandledRejection when returning promises from routes

We would like to start using awilix-express moving forward, but promise rejections are not handled as we expected. Using express-promise-router, our pattern is this:

all routes return promises
When an error happens, throw immediately.
register an error handler (app.use) to inspect the error and return the appropriate response.

I noticed #15 relates to this issue.

But in looking at the code, I see that the error is re-thrown after calling next. I think this is causing the issue.

function asyncErrorWrapper(
  fn: (...args: any[]) => any
): (...args: any[]) => any {
  return function asyncWrappedMiddleware(
    req: Request,
    res: Response,
    next: NextFunction
  ) {
    const returnValue = fn(req, res, next)

    if (
      returnValue &&
      returnValue.catch &&
      typeof returnValue.catch === 'function'
    ) {
      return returnValue.catch((err: any) => {
        next(err)
        throw err // <-- Does this need to be here?
      })
    } else {
      return returnValue
    }
  }
}

Looking at express-promise-router, they don't re-throw in this situation:

// Call the route
var ret = handler.apply(null, args);

// If it doesn't return a promise, we exit.
if (!isPromise(ret)) {
    return;
}

// Since we got a promise, we handle calling next
Promise.resolve(ret).then(
    function(d) {
        if (d === 'next') {
            next();
        } else if (d === 'route') {
            next('route');
        }
    },
    function(err) {
        if (!err) {
            err = new Error('returned promise was rejected but did not have a reason');
        }
        next(err); // <-- no rethrow
    }
);

Maybe I'm not using the library correctly?

Trying minimal example

I have this project structure:

src:
 server.js
 routes
    todos.js

server.js

const Express = require ('express')
const { asClass, createContainer } = require('awilix')
const { loadControllers, scopePerRequest } = require ('awilix-express')


const app = Express()

class TodosService{
  getTodos (){ return "Holi"}
}

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

routes/todos.js

const { makeInvoker } = require('awilix-express')

function makeAPI({ todosService }) {
  return {
    getTodos: (req, res) => {
      return res.send(todosService.getTodos())
    }
  }
}


module.exports = function(router) {
  const api = makeInvoker(makeAPI)
  router.get('/todos', api('getTodos'))
}

I cannot get any response @GET/todos

Is that related to the usage of module.exports instead of exports?
Any insight in how to make this work?
Thanks in advance!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.