typegraphql-nestjs: Middleware causes error: Cannot read property 'isDependencyTreeStatic' of undefined

Hello, I want to use a Middleware class (that manages if a field is returned or not based on if the customer has an active premium subscription or not).

export class PremiumFeature implements MiddlewareInterface {
  constructor() {}

  async use({ context, info }: ResolverData, next: NextFn) {
    return next();
  }
}

for certain fields eg:

@ObjectType()
@InputType('BusinessInput')
@Entity()
export class Business extends StandardEntity {

  @UseMiddleware(PremiumFeature)
  @Field(field => [Card], { nullable: true })
  @OneToMany(
    () => Card,
    card => card.business,
  )
  public cards: Card[];
}

A sample request:

query business($id: Int!) {
  business(id: $id) {
    id
    cards{id}
  }
}

returns the following response with the error:

{
  "errors": [
    {
      "message": "Cannot read property 'isDependencyTreeStatic' of undefined",
      "locations": [
        {
          "line": 4,
          "column": 5
        }
      ],
      "path": [
        "business",
        "cards"
      ],
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR",
        "exception": {
          "stacktrace": [
            "TypeError: Cannot read property 'isDependencyTreeStatic' of undefined",
            "    at Object.get (/media/jim/Daten/projets/place_to_be/code/api/node_modules/typegraphql-nestjs/dist/typegraphql-options.factory.js:42:42)",
            "    at IOCContainer.getInstance (/media/jim/Daten/projets/place_to_be/code/api/node_modules/type-graphql/dist/utils/container.js:39:26)",
            "    at dispatchHandler (/media/jim/Daten/projets/place_to_be/code/api/node_modules/type-graphql/dist/resolvers/helpers.js:74:65)",
            "    at Object.applyMiddlewares (/media/jim/Daten/projets/place_to_be/code/api/node_modules/type-graphql/dist/resolvers/helpers.js:88:12)",
            "    at /media/jim/Daten/projets/place_to_be/code/api/node_modules/type-graphql/dist/resolvers/create.js:75:26",
            "    at field.resolve (/media/jim/Daten/projets/place_to_be/code/api/node_modules/apollo-server-core/dist/utils/schemaInstrumentation.js:52:26)",
            "    at resolveField (/media/jim/Daten/projets/place_to_be/code/api/node_modules/graphql/execution/execute.js:464:18)",
            "    at executeFields (/media/jim/Daten/projets/place_to_be/code/api/node_modules/graphql/execution/execute.js:292:18)",
            "    at collectAndExecuteSubfields (/media/jim/Daten/projets/place_to_be/code/api/node_modules/graphql/execution/execute.js:748:10)",
            "    at completeObjectValue (/media/jim/Daten/projets/place_to_be/code/api/node_modules/graphql/execution/execute.js:738:10)"
          ]
        }
      }
    }
  ],
  "data": {
    "business": {
      "id": 59,
      "cards": null
    }
  }
}

The resolver class is using resolver inheritance, it contains this function:

@Authorized()
  @Query(() => Business, { name: 'business' })
  protected async getOne(@Arg('id', () => Int) id: number) {
    const result =  await this.businessService.get(id, {
      relations: [
        'openingHours',
        'tags',
        'tags.parentTag',
        'tags.childTags',
        'headerImages',
        'cards'
      ],
    });
    return result;
  }

I’d be happy to know what I can do to resolve this issue. Kind regards.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 23 (8 by maintainers)

Most upvoted comments

@yusufkandemir I am the same as you using typegraphql and typegraphql-prisma with Nest.js. Class based middleware really does not work. The workaround I found is to use graphql context instead of dependency injection and use function based middleware.

@MichalLytek Not only my opinion the stack Nest.js + type-graphql + typegraphql-prisma + typegraphql-nestjs is one of the best really. And the only problem in it is the typegraphql-nestjs package.

@valerii15298 Sure you can use context to inject deps to middleware or resolvers. But it’s getting hard if you need that dependency in another layer like service, then you rework the Nest’s DI in your context creation function.

It might be also a bit tricky to mimick the rest of Nest’s DI like scopes, etc. So the bug has to be fixed, shouldn’t be too hard as I think it’s just about collecting options and parsing providers and metadata storage.

@nathan2slime thats expected, you can see above in the thread(only global middlewares work with nest DI).

But for all dependencies you can use approach with graphql context that I pointed out above.

When @MichalLytek will have time he will work on it, but with ability to use graphql context for all deps I don’t think it is a priority feature.

@nathan2slime from you example, maybe try to export it:

@Module({
  triangular:[TypeOrmModule.forFeature([User])],
  providers: [UserResolver, UserService],
  exports: [UserService]
})
export class UserModule {}

Reopening to track non-global middlewares 👀

Class based non-global middlewares not working here either. Any updates on this issue? I thank

Just fixing it for the imports property, in case anyone comes to see it. It worked here thanks for the help!

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  providers: [UserResolver, UserService],
  exports: [UserService],
})
export default class UserModule {}

I am not sure about the details. Function-based middleware can be used per method, the docs mention class-based middleware to be able to use injections, but they can only be used as global middleware and not per-method middleware. I am using TypeGraphQL with Nest integration instead of Nest’s own implementation because this is the original one, you were planning great features, and there is the Prisma integration.

Anyways, since class-based middleware doesn’t work, it’s not possible to use Nest’s middleware, interceptor, etc. system with TypeGraphQL, and createMethodDecorator doesn’t expose the target to be able to inject some stuff onto it, I was basically left with two options: Migrate away from TypeGraphQL to Nest’s own implementation or find other ways to achieve this by creating a plain decorator:

// See https://stackoverflow.com/a/60608856 for implementation details of a decorator with injections
export function LogWhatever() {
  const injectPrisma = Inject(PrismaService);

  return (
    target: object,
    propertyKey: string,
    propertyDescriptor: PropertyDescriptor,
  ) => {
    injectPrisma(target, 'prisma');

    const originalMethod = propertyDescriptor.value as (
      ...args: any[]
    ) => Promise<Whatever>;

    propertyDescriptor.value = async function (...args: any[]) {
      // @ts-expect-error We can't have accurate types for 'this'
      const prisma = this.prisma as PrismaService;

      // We execute the method, save the return value, use it to create a log entry, and return the result
      const whatever = await originalMethod.apply(this, args);

      void createWhateverLog(prisma, whatever);

      return whatever;
    };
  };
}

It doesn’t seem to possible to inject the GraphQL context, info, etc. so it seems that the only way out when needing those is to migrate away from TypeGraphQL unfortunately.

The checks are to collect metadata about TypeGraphQL related stuff. So if we need global middleware metadata, we should have another if block with another checks? I don’t know, hard to guess, it’s been a while since I worked on this. I never thought it will be used by community when Nest has built-in graphql competitive implementation.