graphqlapolloapollo-clientapollo-server

Datasource is undefined when running the Apollo GraphQL server


I am getting datasource undefined on resolvers when trying to query getTracking.

I have a FedexAPI class that extends RESTDataSource from "@apollo/datasource-rest" as a Data source. The class has getTracking method that gets the tracking status of a shipemnt upon passing tracking number. The class exports default FedexAPI. API and end point worked perfect on Postman.

I have this server initiation code:

const server = new ApolloServer({
  typeDefs,
  resolvers,
  dataSources: () => {
    console.log("Testtest");
    const fedexAPI = new FedexAPI();
    console.log("fedexAPI", fedexAPI);
    return {
      fedexAPI,
    };
  },
});

const { url } = await startStandaloneServer(server, {
  listen: { port: 4000 },
  context: async () => ({}),
});

Resolver code:

export const resolvers = {
  Query: {
    trackingInfo: async (_, { trackingNumber }, { dataSources }) => {
      console.log("Data Source", dataSources, trackingNumber);
    
      try {
        return await dataSources.fedexAPI.getTracking(trackingNumber);
      } catch (error) {
        console.error("Error while fetching tracking info:", error);
        throw new Error("Failed to retrieve tracking information");
      }

      return dataSources.fedexAPI.getTracking(trackingNumber);
    },
  },
};

I tried to create an instance of the FedEx class and it successully created FedEx object on my index file.

I am able to access the trackigNumber when using the query trackingInfo. However, datasource is undefined on resolver file.


Solution

  • Looking at your code it looks like you're using @apollo/server (version 4), which has different implementation from apollo-server (versions 1-3). Specifically, dataSources isn't a built-in thing anymore. Instead, you have to add that to the context yourself.

    const server = new ApolloServer({
      typeDefs,
      resolvers,
    });
    
    const { url } = await startStandaloneServer(server, {
      listen: { port: 4000 },
      context: async () => {
        return {
          dataSources: {
            fedexAPI: new FedexAPI(),
          },
        };
      },
    });
    

    I don't know that the Apollo team ever explicitly explained why, but my assumption is that for the various implementations (lambda vs express vs...), creating the dataSources could be done slightly differently (e.g. if you're using express and you want to pass req.session. in the constructor, but if you're doing it via lambda, you might use event.userData.

    That also means, however, that you don't even need to call them "Data Sources" anymore, so you could call them "loaders" or "fetchers" or "models" or something else, if "Data Sources" mean something else in your business.