maticzav / graphql-middleware

Split up your GraphQL resolvers in middleware functions

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

applyMiddleware corrupts schema with Apollo federation `stub types`

jekabs-karklins opened this issue · comments

We are using Apollo federation https://www.apollographql.com/docs/federation/federation-spec/ where gateway combines two graphql schemas into one unified schema.

We have vanilla ordinary setup for it

We have type user in service A

@ObjectType()
@Directive('@key(fields: "id")')
export class User {
  @Field(() => Int)
  public id: number;

  @Field()
  public firstname: string;

  @Field()
  public lastname: string;
}

And we have type stub type User in service B

@ObjectType()
@Directive('@extends')
@Directive('@key(fields: "id")')
export class User {
  @Directive('@external')
  @Field(() => Int)
  id: number;
}

And we have type Equipment in service B that refers to the user

import { User } from './User';

@ObjectType()
export class Equipment {
  @Field(() => ID)
  id: number;

  // external type
  @Type(() => User)
  @Field({ nullable: true })
  owner?: User;

  @Field(() => Date)
  createdAt: Date;
}

By applying this trivial middleware in the service A

const doNothing: IMiddlewareResolver = async (
  resolve,
  root,
  args,
  context,
  info
) => {
  return resolve(root, args, context, info);
};

GraphQL will be unable to resolve external fields for the User. i.e.
This will work

query {
  equipments {
    id
    name
    owner {
      id
    }
  }
}

But this will start to fail

query {
  equipments {
    id
    name
    owner {
      id
      lastname
    }
  }
}

with error message
Cannot return null for non-nullable field User.lastname.

I was unable to pinpoint what is going on, but I see that the schemas ar slightly different after running applyMiddleware(doNothing)

@jekabs-karklins have you found solution?

I have similar schema design to yours and getting similar error. I was using v4.0.2 everything was working expected but recently I have update to latest version(v6.0.10) and then started getting this issue. I have also tested v4.0.3 seems like working everything as expected but not working in 5.0.0 and above

I have also tested v4.0.3 seems like working everything as expected but not working in 5.0.0 and above

Well, I tested and yes, the last working version is v4.0.3. I would like to know if there will be a solution, because working with a 2 years outdated version isn't ideal.

Also huge thanks, this lib is awesome.

hi guys have anyone found a solution?
it is been bugging me with graphql-sheild, resolveRefrence never get called in User service if applyMiddleware is used,
but if removed everything working again.

I tried to applyMiddleware before adding the reference resolvers to the schema and I think that worked without any issues. So it could be a potential/workaround solution to our specific problem. So here is some example code:

 let federatedSchema = buildSubgraphSchema({
    typeDefs: gql(printSubgraphSchema(schema)),
    resolvers: createResolversMap(
      schema
    ) as GraphQLResolverMap<ResolverContext>,
  });

 federatedSchema = applyMiddleware(federatedSchema, logger);

 addResolversToSchema(federatedSchema, referenceResolvers);

if you try to applyMiddleware after adding the resolvers it ends up messing the federation schema and it doesn't resolve the referenceResolvers correctly.

@jekabs-karklins have you found solution?

I have similar schema design to yours and getting similar error. I was using v4.0.2 everything was working expected but recently I have update to latest version(v6.0.10) and then started getting this issue. I have also tested v4.0.3 seems like working everything as expected but not working in 5.0.0 and above

You can try out what @martin-trajanovski is suggesting

I found a solution. You need to check whether the resolver is one of the type fields for the Apollo Federation spec, and simply make your middleware a no-op when it matches.

(TypeScript)

function isFederationIntrospectionQuery({
  prev,
  key,
  typename,
}: GraphQLResolveInfo['path']): boolean {
  if (prev) {
    return isFederationIntrospectionQuery(prev);
  } else {
    return (key == '_entities' || key == '_service') && typename == 'Query';
  }
}

// ...

applyMiddleware(
      schema,
      async (resolve, root, args, context, info) => {
        if (isFederationIntrospectionQuery(info.path)) {
          // no-op
          return resolve(root, args, context, info);
        }
        
        // etc...

Could this logic get rolled into graphql-middleware, or some utility or option be added to make this easier?