Inversifyjs: Provide Injection of incoming request in express-utils

Created on 28 Sep 2016  路  10Comments  路  Source: inversify/InversifyJS

Hi,

it would be great to inject incoming request in a service. Something like:

@inject(TYPES.IncomingRequest) 
private var request;

The problem is that I must pass the request from controll to service everywhere when I need to handle some headers in my services. (e.g.: Reading access token from header)

Most helpful comment

@remojansen the whole race condition could be avoided by modifying inversify-express-utils to use a child container to resolve each controller method.

private handlerFactory(controllerName: any, key: string, parameterMetadata: interfaces.ParameterMetadata[]): express.RequestHandler {
        return (req: express.Request, res: express.Response, next: express.NextFunction) => {
            let container = this._container.createChild();
            container.bind<express.Request>(TYPE.Request).toConstantValue(req);
            let args = this.extractParameters(req, res, next, parameterMetadata);
            let result: any = container.getNamed(TYPE.Controller, controllerName)[key](...args);
            Promise.resolve(result)
                .then((value: any) => {
                    if (value && !res.headersSent) {
                        res.send(value);
                    }
                })
                .catch((error: any) => {
                    next(error);   });
        };
    }

All 10 comments

Hi @nodify-at,

Thanks a lot for using inversify and sharing your feedback with us 馃憤

I see why you need it but I don't know if I like the idea of adding this as a feature because it will require all repositories to have a property named request. I don't like this because it forces the repositories to implement an interface and I think it is better to give freedom to our users and avoid forcing the implementation of an interface or extension of a base class.

Also, based on the SOLID principles and the separation of concerns principle, your repositories should not be aware of your controllers, or any kind of service related concerns like headers or requests. That is the job of your controllers. Your repositories should only be concern with data access stuff.

You should read the token in the controller and then pass it to the repository. The repository needs a token but it doesn't need to know that it comes from a request header.

Also this is not easy to implement at the moment because multiple request can be taking place at a given moment. So when we use @inject(TYPES.IncomingRequest) which of the requests that the server is processing should be injected? Remember that the Inversify kernel lives at the application scope not session or request scope.

I have plans to add custom scope support to inversify and request/session scope to the express utils but even them this would not be possible.

I would like to know what thinks @codyjs ?

Maybe I misunderstood the question, but it sounds like @remojansen is right. I don't see why you would inject the request into anything. The request should be sent to the controller and passed along from there. Nothing "depends" on the request, it's just some data that's been sent to the server, and its lifetime is finite.

If you need to read an access token from the request, you can either send the request from the controller like this:

@Get('/')
getSomething(req, res) {
    let token = this.service.getToken(req);
    // ...
}

Or, preferably, you should use middleware, like this:

@Get('/', setAuthToken)
getSomething(req, res) {
    // this was set in setAuthToken()
    let token = req.token;
    // ...
}

You might find this example helpful.

Hi @nodify-at can this issue be closed?

Hi @remojansen ,

thank you for your support. I think you're right. We can close the ticket, I've solved my problem by using a middleware.

Hi @remojansen, @codyjs

I completely agree that the request object should only be accessible up to the controller layer. However, cross-cutting concerns like identity (user context) in a web app (i.e. express) are usually extracted from the session or some other property/token stored in the request object.

Is there a way to make the user context injectable into the application services using Inversify.js while maintaining a good separation of concerns? It seems we're lacking the concept of request or session scopes to achieve that... Or is there another way? Thanks!

@Get('/')
getSomething(req, res) {
    // How can I make this injectable?
    let userCtx:UserContext = () => new User(req.session.user);
   // ...
}

...

@injectable()
export class  MyService {

    // So that I can inject it whenever I need it...
    constructor(
        @inject('UserContext') private _userCtx : UserContext;
    ){ }

    myFunction() {

        const user = this._userCtx();
        // do smth with the user info...

    }


}


Hi @julianosam las week I implemented and released support for inRequestScope.

We refer to each call to the container.get as a container request. The inRequestScope uses the same instance for all the sub-dependencies in one unique container request. It should help in this case.

In the inversify-express-utils architecture, the container.get method is invoked by the router:

  • Each HTTP request will be handled by one route.
  • Each route leads to 1 unique call to container.get

These means that we can assume that we can map 1 HTTP call to 1 Container Request.

So, in theory, we can try something similar to the following:

const TYPE = {
    CurrentRequest : Symbol("CurrentRequest")
    UserContext: Symbol("UserContext")
};

function setup(cntnr) {

    const server = new InversifyExpressServer(cntnr);

    //  Create a middleware to bind the current request to a type
    const currentRequestBindingMiddleware = (req, res, next)  => {
       cntnr.isBound(TYPE.CurrentRequest) {
           cntnr.rebind<Request>(TYPE.CurrentRequest)
                .toConstantValue(req)
                .inRequestScope();
       } else {
           cntnr.bind<Request>(TYPE.CurrentRequest)
                .toConstantValue(req)
                .inRequestScope();
       }
       next();
    };

    server.setConfig((app) => {
        app.use(bodyParser.urlencoded({**  extended: true  }));
        app.use(bodyParser.json());
        app.use(helmet());
        app.use(compress());
        // It shoud bind the request before routing takes place for all requests
        app.use(currentRequestBindingMiddleware(cntnr));
    });

    return server;
}

const container = new Container();

// We can then declare a dynamic binding that creates
// a binding for UserContext using the current request
container.bind<UserContext>(TYPE.UserContext).toDynamicValue((context) => {
    const currentRequest = context.get<Request>(TYPE.CurrentRequest);
    return new UserContext(currentRequest);
}).inRequestScope();

const server = setup(container);

// ...

@injectable()
@controller("/")
export class  MyService {

    @inject(TYPE.UserContext) private _userContext : UserContext;

    @httpGet('/')
    public get(): string {
        const user = this._userContext.get();
        return `Hello ${user.name}!`;
    }

}

What should happen then is the following:

  1. An HTTP request thits the server.
  2. A binding is declared for TYPE.CurrentRequest and the current request.
  3. Routing takes places and container.get is invoked.
  4. The container tries to resolve TYPE.UserContext because it is needed by a Contoller.
  5. The TYPE.CurrentRequest resolved value is added to the inRequestScope.
  6. The TYPE.UserContext resolved value is added to the inRequestScope.
  7. The TYPE.UserContext resolved value controller is injected in to a Contoller.

:warning: This problem is not as simple as it may look because the Container is an application singleton. This means that we have one container that is shared across multiple requests. So we need to test this properly with many parallel requests to ensure that there are no rare conditions.

@remojansen the whole race condition could be avoided by modifying inversify-express-utils to use a child container to resolve each controller method.

private handlerFactory(controllerName: any, key: string, parameterMetadata: interfaces.ParameterMetadata[]): express.RequestHandler {
        return (req: express.Request, res: express.Response, next: express.NextFunction) => {
            let container = this._container.createChild();
            container.bind<express.Request>(TYPE.Request).toConstantValue(req);
            let args = this.extractParameters(req, res, next, parameterMetadata);
            let result: any = container.getNamed(TYPE.Controller, controllerName)[key](...args);
            Promise.resolve(result)
                .then((value: any) => {
                    if (value && !res.headersSent) {
                        res.send(value);
                    }
                })
                .catch((error: any) => {
                    next(error);   });
        };
    }

Thanks so much for the reply @remojansen ! Will give it a try soon!

Hi there @remojansen ...

sorry for bringing it up again.

I tried the example you illustrated above to bind an object to the http request scope.

cntnr.bind(TYPE.CurrentRequest)
.toConstantValue(req)
.inRequestScope();

It looks like the .inRequestScope is not allowed on a Constant.

Is there another way to accomplish that? I looked into the HttpContext injection stuff which would basically work but wouldn't be as "elegant" ...

Hope you can shed some light on that topic?

Thanks and greets,
Wolfgang

Was this page helpful?
0 / 5 - 0 ratings