apollographql/apollo-link

SchemaLink in server and dataSources

Opened this issue · 3 comments

I am using apollo server and i am in need to run queries on that so called server not for SSR but for some backend logic to avoid doing network calls out to localhost and back in to localhost i am using

ApolloClient with SchemaLink after a few time reading doc and trial error i have it running, however
i am using DataSources on my server and i can't seem to find how to getting it working with SchemaLink



let DataSources = () => {
    return {
        Api: new ApiDataSource(),
        Auth: new AuthDataSource()
    }
};

let schemaLink = new SchemaLink({
            schema: Schema,
            context: () => {
                let token = '.....';
                const data: any = jwt.verify(token, Buffer.from(process.env.JWT_SECRET, 'base64'));

                const loaders = {
                    getMongoUsersByIds: new DataLoader(getMongoUsersByIds)
                };

                return {
                    loaders: loaders,
                    userToken: data,
                    dataSources: DataSources()
                }
            }
        });


        this.client = new ApolloClient({
            ssrMode: true,
            link: schemaLink,

            cache: new InMemoryCache(),
        });

as you can see i have tried to include the datasources in the context of SchemaLink but when it reaches any resolver that uses one of the two datasources i get Error: GraphQL error: Cannot read property 'fetch' of undefined

This is probably because fetch is being called as window.fetch(), you can provide your own fetch function to the apollo-link-http link.

I've run into the same issue, and dove into the code to try to figure out what's going on here. And I think the "high level" problem here is that the initialize method is never getting called on the datasources.

To elaborate: the RESTDataSource has an internal HttpCache instance, and all requests are actually done through that (which ultimately uses fetch if it ends up making an HTTP request and not using its cache). The problem is that the RESTDataSource's httpFetch instance doesn't actually get created until initialize is called. And this typically is done by Apollo Server as part of the request pipeline.

But with SchemaLink, we never go through that pipeline. So dataSources never get initialized (among other things), and when RESTDataSource ultimately tried to call this.httpCache.fetch, this.httpCache is actually undefined, which is where the error Cannot read property 'fetch' of undefined is originating from.

Now, you could call initialize yourself. Perhaps like this:

const apolloLink = new SchemaLink({
      schema: Schema,
      context: (operation): ResolverContext => {
        const { cache } = operation.getContext();

        // Assuming `dataSources` exists as an object map at this point.
        // This is where you'd add other things to your context, too.
        const context = { 
          dataSources 
        }; 

        // Here is where we manually call `initialize` for our datasources.
        // WARNING: This isn't technically correct in all scenarios! See below.
        Object.values(dataSources).forEach(dataSource => {
          if (dataSource.initialize) {
            dataSource.initialize({
              context,
              cache,
            });
          }
        });

        return context;
      },
    });

One big problem here is that technically initialize can be async and return a promise. But we can't really handle that here because the ResolverContextFunction isn't async. This isn't (currently!) a problem with RESTDataSource because its initialize function is synchronous, but we can't guarantee that won't change, or that all of your datasources use synchronous initialize methods.

There's also some other problems I've run into that I can elaborate on if needed. But I think ultimately the problem is that SchemaLink is not the "drop-in" replacement for server-side rendering that it's currently billed as. It bypasses a bunch of the graphql request pipeline and so you see a lot of unexpected things happening. And I don't know what else may be hiding under-the-hood beyond this. I wish the docs were more clear about this instead, because to me they make it seem like you can just replace your HttpLink with SchemaLink and everything will be great.

Perhaps I'm missing something or I'm misunderstanding (I hope!), but unfortunately right now, based on what I'm seeing, I just don't think SchemaLink is really viable except in the absolute simplest of cases. Hoping someone can maybe offer some additional insight or alternatives.

Quick update for anyone that comes across this: It seems you want to use the cache object from your ApolloServer instance, and not the one found from operation.getContext(). i.e.:

const graphqlServer = new ApolloServer();

// later, when initializing dataSources in your `SchemaLink`;
const apolloLink = new SchemaLink({
  context: (operation): ResolverContext => {
    const context = { dataSources };
    
    Object.values(dataSources).forEach(dataSource => {
      if (dataSource.initialize) {
        dataSource.initialize({
          context,
          // Important difference is here!:
          cache: graphqlServer.requestOptions.cache,
        });
      }
    });
    
    return context;
  }
});

Unfortunately the async issues still exists here.