Fetching Data

Manage connections to databases and other data sources


Looking to fetch data from a REST API? Check out Fetching from REST.

Apollo Server can fetch data from any source you need, such as a REST API or a database. Your server can use any number of different data sources:

tip
Learn how Apollo Connectors simplify incorporating REST APIs into your graph.

Because your server can use multiple various data sources, keeping your resolvers tidy becomes even more important.

For this reason, we recommend creating individual data source classes to encapsulate the logic of fetching data from a particular source, providing methods that resolvers can use to access data neatly. You can additionally customize your data source classes to help with caching, deduplication, or errors while resolving operations.

Creating data source classes

Your data source class can be as straightforward or complex as you need it to be. You know what data your server needs, and you can let that be the guide for the methods your class includes.

Below is an example of a data source class that connects to a database storing reservations:

TypeScript
reservations.ts
1export class ReservationsDataSource {
2  private dbConnection;
3  private token;
4  private user;
5
6  constructor(options: { token: string }) {
7    this.dbConnection = this.initializeDBConnection();
8    this.token = options.token;
9  }
10
11  async initializeDBConnection() {
12    // set up our database details, instantiate our connection,
13    // and return that database connection
14    return dbConnection;
15  }
16
17  async getUser() {
18    if (!this.user) {
19      // store the user, lookup by token
20      this.user = await this.dbConnection.User.findByToken(this.token);
21    }
22    return this.user;
23  }
24
25  async getReservation(reservationId) {
26    const user = await this.getUser();
27    if (user) {
28      return await this.dbConnection.Reservation.findByPk(reservationId);
29    } else {
30      // handle invalid user
31    }
32  }
33
34  //... more methods for finding and creating reservations
35}

Apollo's RestDataSource class is a built-out example of how a data source class can handle caching, deduplication, and errors.

Batching and caching

If you want to add batching, deduplication, or caching to your data source class, we recommend using the DataLoader package. Using a package like DataLoader is particularly helpful for solving the infamous N+1 query problem.

DataLoader provides a memoization cache, which avoids loading the same object multiple times during a single GraphQL request (much like one of RESTDataSource's caching layers). It also combines loads during a single event loop tick into a batched request that fetches multiple objects at once.

DataLoader instances are per-request, so if you use a DataLoader in your data source, ensure you create a new instance of that class with every request :

TypeScript
1import DataLoader from 'dataloader';
2
3class ProductsDataSource {
4  private dbConnection;
5
6  constructor(dbConnection) {
7    this.dbConnection = dbConnection;
8  }
9
10  private batchProducts = new DataLoader(async (ids) => {
11    const productList = await this.dbConnection.fetchAllKeys(ids);
12    // Dataloader expects you to return a list with the results ordered just like the list in the arguments were
13    // Since the database might return the results in a different order the following code sorts the results accordingly
14    const productIdToProductMap = productList.reduce((mapping, product) => {
15        mapping[product.id] = product;
16        return mapping;
17    }, {});
18    return ids.map((id) => productIdToProductMap[id]);
19  });
20
21  async getProductFor(id) {
22    return this.batchProducts.load(id);
23  }
24}
25
26// In your server file
27
28// Set up our database, instantiate our connection,
29// and return that database connection
30const dbConnection = initializeDBConnection();
31
32const { url } = await startStandaloneServer(server, {
33  context: async () => {
34    return {
35      dataSources: {
36        // Create a new instance of our data source for every request!
37        // (We pass in the database connection because we don't need
38        // a new connection for every request.)
39        productsDb: new ProductsDataSource(dbConnection),
40      },
41    };
42  },
43});

Adding data sources to your context function

In the examples below, we use top-level await calls to start our server asynchronously. Check out our Getting Started guide to see how we configured our project to support this.

You can add data sources to your server's context initialization function, like so:

TypeScript
index.ts
1interface ContextValue {
2  dataSources: {
3    dogsDB: DogsDataSource;
4    catsApi: CatsAPI;
5  };
6  token: string;
7}
8
9const server = new ApolloServer<ContextValue>({
10  typeDefs,
11  resolvers,
12});
13
14const { url } = await startStandaloneServer(server, {
15  context: async ({ req }) => {
16    const { cache } = server;
17    const token = req.headers.token;
18    return {
19      // We create new instances of our data sources with each request.
20      // We can pass in our server's cache, contextValue, or any other
21      // info our data sources require.
22      dataSources: {
23        dogsDB: new DogsDataSource({ cache, token }),
24        catsApi: new CatsAPI({ cache }),
25      },
26      token,
27    };
28  },
29});
30
31console.log(`🚀  Server ready at ${url}`);

Apollo Server calls the context initialization function for every incoming operation. This means:

  • For every operation, context returns an object containing new instances of your data source classes (in this case, DogsDataSource and CatsAPI).

  • If your data source is stateful (e.g., uses an in-memory cache), the context function should create a new instance of your data source class for each operation. This ensures that your data source doesn't accidentally cache results across requests.

Your resolvers can then access your data sources from the shared contextValue object and use them to fetch data:

TypeScript
resolvers.ts
1const resolvers = {
2  Query: {
3    dog: async (_, { id }, { dataSources }) => {
4      return dataSources.dogsDB.getDog(id);
5    },
6    popularDogs: async (_, __, { dataSources }) => {
7      return dataSources.dogsDB.getMostLikedDogs();
8    },
9    bigCats: async (_, __, { dataSources }) => {
10      return dataSources.catsApi.getCats({ size: 10 });
11    },
12  },
13};

Open-source implementations

Apollo Server 3 contained an abstract class named DataSource that each of your data sources could subclass. You'd then initialize each of your DataSource subclasses using a special dataSources function, which attaches your data sources to your context behind the scenes.

In Apollo Server 4, you can now create your data sources in the same context function as the rest of your per-request setup, avoiding the DataSource superclass entirely. We recommend making a custom class for each data source, with each class best suited for that particular source of data.

Modern data sources

Apollo maintains the following open-source data source for Apollo Server 4:

ClassExamplesFor Use With
RESTDataSourceSee Fetching RestHTTP/REST APIs

The community maintains the following open-source data sources for Apollo Server 4:

ClassSourceFor Use With
BatchedSQLDataSourceCommunitySQL databases (via Knex.js) & Batching (via DataLoader)
FirestoreDataSourceCommunityCloud Firestore

Legacy data source classes

⚠️ Note: The community built each data source package below for use with Apollo Server 3. As shown below, you can still use these packages in Apollo Server 4 with a bit of extra setup.

The below data source implementations extend the generic DataSource abstract class, from the deprecated apollo-datasource package. Subclasses of DataSource define the logic required to communicate with a particular store or API.

The larger community maintains the following open-source implementations:

ClassSourceFor Use With
HTTPDataSourceCommunityHTTP/REST APIs
SQLDataSourceCommunitySQL databases (via Knex.js)
MongoDataSourceCommunityMongoDB
CosmosDataSourceCommunityAzure Cosmos DB

Apollo does not provide official support for community-maintained libraries. We cannot guarantee that community-maintained libraries adhere to best practices, or that they will continue to be maintained.

Using DataSource subclasses

In Apollo Server 3, immediately after constructing each DataSource subclass, your server would invoke the initialize({ cache, context }) method on each new DataSource behind the scenes.

To replicate this in Apollo Sever 4, you can manually invoke the initialize method in the constructor function of each DataSource subclass, like so:

TypeScript
1import { ApolloServer } from '@apollo/server';
2import { startStandaloneServer } from '@apollo/server/standalone';
3import { KeyValueCache } from '@apollo/utils.keyvaluecache';
4import { Pool } from 'undici';
5import { HTTPDataSource } from 'apollo-datasource-http';
6
7class MoviesAPI extends HTTPDataSource {
8  override baseURL = 'https://movies-api.example.com/';
9
10  constructor(options: { cache: KeyValueCache<string>; token: string }) {
11    // the necessary arguments for HTTPDataSource
12    const pool = new Pool(baseURL);
13    super(baseURL, { pool });
14
15    // We need to call the initialize method in our data source's
16    // constructor, passing in our cache and contextValue.
17    this.initialize({ cache: options.cache, context: options.token });
18  }
19
20  async getMovie(id: string): Promise<Movie> {
21    return this.get<Movie>(`movies/${encodeURIComponent(id)}`);
22  }
23}
24
25interface MyContext {
26  dataSources: {
27    moviesApi: MoviesAPI;
28  };
29  token?: string;
30}
31
32const server = new ApolloServer<MyContext>({
33  typeDefs,
34  resolvers,
35});
36
37const { url } = await startStandaloneServer(server, {
38  context: async ({ req }) => {
39    const { cache } = server;
40    const token = req.headers.token;
41    return {
42      dataSources: {
43        moviesApi: new MoviesAPI({ cache, token }),
44      },
45      token,
46    };
47  },
48});
Feedback

Edit on GitHub

Forums