Apollo Server: Initializing Data Sources

When initializing Apollo server, you may be doing something similar to this:

const items = new ItemsDataSource();
const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: () => { ... },
  dataSources: () => {
    return { items };
  },
});

There’s a danger to having your data sources constructed when Apollo server is initialized like this, especially if you’re calling initialize in your data sources like so:

initialize(context) {
   this.context = context;
}

function addItem({ item }) {
  const user = this.context.getUser();
  // ...
}

In a web server environment such as Express, the context can change across requests. Because of this, if you’re using data from the context within functions in your data source, you risk having data shared across requests that you’re likely not expecting.

For instance, if you’re fetching todo list items in ItemsDataSource like this:

function getItems() {
  const user = this.context.getUser();
  const items = this.itemsStore.find({ ownerId: user.id });
  return items;
}

You likely won’t see any problem in local development, but as multiple requests in production come in from various users, you’ll likely notice getItems() returning data for user B when you’re expecting data for user A! Ouch!

I prefer not to have data sources bound so tightly to the context. So, instead of the pattern above, I think this makes more sense:

function getItems({ user }) {
  if (!user) {
    throw new Error("Yo, we don’t got no user, so you get no items!");
  }
  const items = this.itemsStore.find({ ownerId: user.id });
  return items;
}

It now becomes the responsibility of the resolver that calls this function to understand the context and how to extract the user from it. This would fix our problem, as changes in the context will no longer be shared across requests and calls to getItems().

But, we can protect ourselves even more by constructing the data sources on every request instead of just once at server initialization:

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: () => { ... },
  dataSources: () => {
    return { items: new ItemsDataSource() };
  },
});

With this change, the data source no longer has any possibility of getting access to data shared across requests and prevents other developers from accidentally doing so. Yay for safety!