apollo-link
apollo-link copied to clipboard
SchemaLink in server and dataSources
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.