inversify-express-utils icon indicating copy to clipboard operation
inversify-express-utils copied to clipboard

Upgrade inversify-express-utils to support inversify 7

Open alexleboucher opened this issue 9 months ago • 15 comments

If I use inversify-express-utils with inversify 7, I have an error :

TypeError: this._container.isBoundNamed is not a function
    at /app/node_modules/inversify-express-utils/lib/server.js:86:33
    at Array.forEach (<anonymous>)
    at InversifyExpressServer.registerControllers (/app/node_modules/inversify-express-utils/lib/server.js:84:22)
    at InversifyExpressServer.build (/app/node_modules/inversify-express-utils/lib/server.js:75:14)

I think it could be a good idea to support inversify 7.

alexleboucher avatar Mar 18 '25 14:03 alexleboucher

Hey @alexleboucher, we are putting all our effords in building better http libraries. These effords are reflected in the monorepo. We will write an entry in the inversify blog announcing all the relevant changes and motivations to go for it. We expect to provide a better support while reducing the overhead as much as possible.

I know how frustrating is not be able to upgrade, specially when inversify@7 has some relevant performant upgrades, but I honestly believe it's going to be worth the wait 🙂

notaphplover avatar Mar 20 '25 18:03 notaphplover

@notaphplover any new blogs in the works or up to date roadmaps we can follow along with? would be great to have a sense of how close we are to getting the new solutions. I really liked the initial ones on v7 and its performance improvements

acommodari avatar Jun 27 '25 16:06 acommodari

Hey @acommodari, it's hard to do a roadmap when contributing in my spare time. Tbh, I don't feel in the mood of coding these days. This is not related to the inversify project, I've had the worst experience in my last job and I'm taking some time to get over a complete burnout. I'm fixing issues though. I hope I feel better soon, but I might need some time to continue the ongoing http integration in the monorepo.

The good news is it's mostly done, we need to release some alpha versions and do some real world applications to understand how good is the user experience. and get feedback.

Last but not least, special kudos to @adrianmjim, he's been helping me a lot these past months with the http integration. 90% of the work was accomplished by him.

notaphplover avatar Jun 28 '25 19:06 notaphplover

@notaphplover I'm very sorry to hear that! Please take all the time you need and I hope you feel better soon! Thank you for all the work you've been putting into the project 😄

acommodari avatar Jul 03 '25 11:07 acommodari

@notaphplover I just wanted to post to ask if there were any updates? And equally, if there's anything I or others can usefully do to contribute at the moment? (Coding, or testing)

Hopefully you're getting over your burnout - I know it can take it's time!

egmacke avatar Aug 28 '25 12:08 egmacke

@notaphplover I just wanted to post to ask if there were any updates? And equally, if there's anything I or others can usefully do to contribute at the moment? (Coding, or testing)

Hopefully you're getting over your burnout - I know it can take it's time!

Hey @egmacke , it's fine to ask, don't worry. We have already implemented controllers, pipes, guards and middlewares for express 4, express 5, fastify and hono. We are implementing validation pipes on top of zod and OpenApi docs on top of SwaggerUI.

We are implementing Exception filters. Once we finish we will release the packages to be used.

We're writting docs as well, you can check them out at ~~https://inversify.io/http~~ https://inversify.io/framework. Keep in mind these docs are provisional and you cannot use the packages.

My dream would be releasing everything before October, but I want to be sure we give you some decent and well tested pieces of code. We want you to enjoy using our framework instead of fighting it, and that's way harder than it looks like.

You can see the progress of our PR in the monorepo :)

notaphplover avatar Aug 28 '25 12:08 notaphplover

@notaphplover that's awesome news. I did think the mono-repo was looking very active!

I've just had a quick poke around both the mono-repo and that docs link - all looking great. And really appreciate the attention to quality!

egmacke avatar Aug 28 '25 13:08 egmacke

@notaphplover Just want to appreciate the work you've been doing. Wish you the best in getting over your burnout. If you need extra hands on something, would love to help out!

suhailsalim avatar Sep 13 '25 10:09 suhailsalim

Hey @suhailsalim, thanks for your kind words. I'm doing great these days 🙂.

If you need extra hands on something, would love to help out!

Actually I do! I'm working on bringing some validation tools to the http layer. The Zod validator is already implemented, but both class validator and ajv need to be implemented as well.

Anyone is welcome to go for them. Just comment at inversify/monorepo#522, telling which validator you are going to implement and feel free to go for it. This way I can focus on other topics, like polishing the exisitng API and writing docs.

notaphplover avatar Sep 13 '25 21:09 notaphplover

Hey guys, I finally released the http packages. You can finally use them as they are described in the docs. Your feedback would be super valuable, if you see any issue on your end please submit it in the monorepo and I'll have it a look

notaphplover avatar Sep 23 '25 12:09 notaphplover

@notaphplover Great news. Excited to try it out.

suhailsalim avatar Oct 01 '25 07:10 suhailsalim

@notaphplover almost finished migrating across. All looking nice so far! One question, is there an equivalent in the new version of AuthProvider, and httpContext? (We made extensive use of it when extending from BaseHttpController - mostly for identifying the calling user)

If it doesn't exist, any pointers on a migration path?

egmacke avatar Oct 15 '25 14:10 egmacke

Hey @egmacke,

is there an equivalent in the new version of AuthProvider, and httpContext? (We made extensive use of it when extending from BaseHttpController - mostly for identifying the calling user)

Not really, at least, not what you probably need. Auth is so complex these days I considered covering it a titanic efford. I delegated in better auth instead.

Having said that, my goal is to listen to your feedback and give the best developer experience possible. Migrating to better auth is probably a good long term solution, but painful in the short term, for better auth takes responsibility in areas like db design.

Providing you a middleware to fetch users and a decorator to allow you to obtain them in any controller route should be more than enough. Having said that, I got the feeling it's sort of a half cooked feature: why wouldn't you like to gain access to the user in a middleware / guard / interceptor? Why just providing you access to users in a controller route? This makes me rethink the approach:

Proposal

  1. AuthMiddleware interface:

Interface to declare a provider able to fetch users given a TRequest.

interface AuthProvider<TRequest = any, TUser> {
   getUser(request: TRequest): TUser | Promise<TUser>
}
  1. AuthMiddleware

Class that receive an AuthProvider and can be used to fetch users. This is just a wrapper so you can focus on getting users via AuthProvider and AuthMiddleware handles the rest.

@injectable()
class AuthMiddleware<
  TRequest = any,
  TResponse = any,
  TNextFunction = any,
  TResult = any,
> extends Middleware<TRequest, TResponse, TNextFunction, TResult> {
  constructor(authProvider: AuthProvider<TRequest, TUser>)
}
  1. Request context related typings and symbol.

A symbol to be used to access user from a request:

export const requestContextSymbol: unique symbol = Symbol.for('well-known-key-unlikelly-to-collide');

And a RequestContext type:

interface RequestContext<TUser> {
  user: TUser;
}

interface RequestWithContext<TUser> {
  [requestContextSymbol]: RequestContext<TUser>
}

This way, you just need to inject an AuthMiddleware in the container, use it in your controller routes and you'll have access to the user via requestContextSymbol:

import { Controller, Get, Request } from '@inversifyjs/http-core';
import { Request as ExpressRequest } from 'express';

@ApplyMiddleware(AuthMiddleware)
@Controller('/warriors')
export class WarriorsGetRequestExpressController {
  @Get()
  public async getWarrior(
    @Request() request: ExpressRequest & RequestWithContext,
  ): Promise<Record<string, string>> {
    const user = request[requestContextSymbol].user;

    // Do something else...
  }
}

I will draft a PR with this so you just need to install a package to get these modules. What do you think about it?

If it doesn't exist, any pointers on a migration path?

In addition, I'll add a migration guide later mentioning this ;)

notaphplover avatar Oct 15 '25 19:10 notaphplover

@notaphplover Thank you for the detailed response. I think it slightly misses what I'm trying to achieve though.

We already have a piece of middleware that handles the authentication process (and attaches various additional info to the request object). The problem is that within a lot of our controllers (several hundred) we need access to the user principal in order to action the requests correctly (based on the calling user (ID, etc))

(Worth noting, we have no need to switch to using better auth because we already use a separate system for managing use authentication, we just need access to the info in the controllers)

The biggest thing that inversify-express-utils HttpContext gave was the ability to access this information in every controller method without having to inject the raw Request object into the method signature.

I appreciate this may be a more niche use case.

Would you be able to advise on how we could implement something like this ourselves (i.e. a custom BaseController) which would provide a getter to access the Request and Response objects directly without needing to use parameter injection on every affected method?

Something like this:

import { Request as ExpressRequest, Response as ExpressResponse } from 'express';

type HttpContext = {
  request: ExpressRequest;
  response: ExpressResponse;
};

abstract class BaseHttpController {
  constructor() {}

  protected get httpContext(): HttpContext {
    const request = ?
    const response = ?

    return {
      request,
      response
    };
  }
}

egmacke avatar Oct 16 '25 08:10 egmacke

Hey @egmacke,

The biggest thing that inversify-express-utils HttpContext gave was the ability to access this information in every controller method without having to inject the raw Request object into the method signature.

I appreciate this may be a more niche use case.

Would you be able to advise on how we could implement something like this ourselves (i.e. a custom BaseController) which would provide a getter to access the Request and Response objects directly without needing to use parameter injection on every affected method?

I see. Tricky, but not impossible. What about asyncLocalStorage.run()? It's stable in node 24.X, which is going to be LTS in a matter of days.

Let me elaborate my idea:

userland/BaseHttpController.ts

import { AsyncLocalStorage } from 'node:async_hooks';
import { injectable } from 'inversify';
import { Request, Response } from 'express';

interface HttpContext {
  request: Request;
  response: Response;
}

// Export the storage so middleware can use it
export const httpContextStorage = new AsyncLocalStorage<HttpContext>();

@injectable()
export abstract class BaseHttpController {
  protected get httpContext(): HttpContext {
    const context = httpContextStorage.getStore();
    if (!context) {
      throw new Error('HttpContext not available outside request scope');
    }
    return context;
  }
}

What if we were able to provide context every time a request is handled? We would have solved this tricky use case. We just need to implement a middleware to accomplish that:

userland/HttpContextMiddleware.ts

import { ExpressMiddleware } from '@inversifyjs/http-express';

import { httpContextStorage } from '../BaseHttpController';

export class HttpContextMiddleware implements ExpressMiddleware {
    async execute(req, res, _next) {
      return httpContextStorage.run({ request: req, response: res }, async () => {
        await next();
      });
    }
 }

Just update your controllers to extend BaseHttpController and apply HttpContextMiddleware as a global middleware and you're ready to go 🙂

notaphplover avatar Oct 16 '25 12:10 notaphplover