Update data sources docs

This commit is contained in:
Martijn Walraven 2018-06-19 23:47:25 +02:00
parent 7f7f70e13c
commit 60c0f587c1

View file

@ -3,78 +3,131 @@ title: Data Sources
description: Caching Partial Query Results description: Caching Partial Query Results
--- ---
Data sources are components that encapsulate loading data from a particular backend, like a REST API, with built in best practices for caching, deduplication, batching, and error handling. You write the code that is specific to your backend, and Apollo Server takes care of the rest. Data sources are classes that encapsulate fetching data from a particular service, with built-in support for caching, deduplication, and error handling. You write the code that is specific to interacting with your backend, and Apollo Server takes care of the rest.
## REST Data Source ## REST Data Source
A `RESTDataSource` is responsible for fetching data from a given REST API. It contains data source specific configuration and relies on convenience methods to perform HTTP requests. A `RESTDataSource` is responsible for fetching data from a given REST API.
To start, install the release candidate of the REST data source: To get started, install the release candidate of the REST data source:
```bash ```bash
npm install apollo-datasource-rest@rc npm install apollo-datasource-rest@rc
``` ```
To define a data source, extend the `RESTDataSource` class. This code snippet shows a data source for the Star Wars API. Note that these requests will be automatically cached based on the caching headers returned from the API. To define a data source, extend the `RESTDataSource` class and implement the data fetching methods that your resolvers require. Your implementation of these methods can call on convenience methods built into the `RESTDataSource` class to perform HTTP requests, while making it easy to build up query parameters, parse JSON results, and handle errors.
```js ```typescript
const { RESTDataSource } = require('apollo-datasource-rest'); const { RESTDataSource } = require('apollo-datasource-rest');
export class StarWarsAPI extends RESTDataSource { class MoviesAPI extends RESTDataSource {
baseURL = 'https://swapi.co/api/'; baseURL = 'https://movies-api.example.com';
async getPerson(id: string) { async getMovie(id: string) {
return this.get(`people/${id}`); return this.get(`movies/${id}`);
} }
async searchPerson(search: string) { async getMostViewedMovies(limit: number = 10) {
return this.get(`people/`, { const data = await this.get('movies', {
search, per_page: limit,
order_by: 'most_viewed',
});
return data.results;
}
}
```
Data sources allow you to intercept fetches to set headers or make other changes to the outgoing request. This is most often used for authorization. Data sources also get access to the GraphQL execution context, which is a great place to store a user token or other information you need to have available.
```typescript
class PersonalizationAPI extends RESTDataSource {
baseURL = 'https://personalization-api.example.com';
willSendRequest(request: Request) {
request.headers.set('Authorization', this.context.token);
}
async getFavorites() {
return this.get('favorites');
}
async getProgressFor(movieId: string) {
return this.get('progress', {
id: movieId,
}); });
} }
} }
``` ```
To create a data source, we provide them to the `ApolloServer` constructor To give resolvers access to data sources, you pass them as options to the `ApolloServer` constructor:
```js ```typescript
const server = new ApolloServer({ const server = new ApolloServer({
typeDefs, typeDefs,
resolvers, resolvers,
dataSources: () => ({ cache: new InMemoryKeyValueCache(maxSize: 1000000),
starWars: new StarWarsAPI(), dataSources: () => {
}), return {
moviesAPI: new MoviesAPI(),
personalizationAPI: new PersonalizationAPI(),
};
},
context: () => {
return {
token:
'foo',
};
}
}); });
``` ```
Apollo Server will put the data sources on the context, so you can access them from your resolvers. It will also give data sources access to the context, which is why they need to be configured separately. Apollo Server will put the data sources on the context for every request, so you can access them from your resolvers. It will also give your data sources access to the context. (The reason for not having users put data sources on the context directly is because that would lead to a circular dependency.)
Then in our resolvers, we can access the data source and return the result: From our resolvers, we can access the data source and return the result:
```js ```typescript
const typeDefs = gql` Query: {
type Query { movie: async (_source, { id }, { dataSources }) => {
person: Person return dataSources.moviesAPI.getMovie(id);
} },
mostViewedMovies: async (_source, _args, { dataSources }) => {
type Person { return dataSources.moviesAPI.getMostViewedMovies();
name: String },
} favorites: async (_source, _args, { dataSources }) => {
`; return dataSources.personalizationAPI.getFavorites();
const resolvers = {
Query: {
person: (_, id, { dataSources }) => {
return dataSources.starWars.getPerson(id);
}, },
}, },
};
``` ```
The raw response from the Star Wars REST API appears as follows: ## What about DataLoader?
```js [DataLoader](https://github.com/facebook/dataloader) was designed by Facebook with a specific use case in mind: deduplicating and batching object loads from a data store. It provides a memoization cache, which avoids loading the same object multiple times during a single GraphQL request, and it coalesces loads that occur during a single tick of the event loop into a batched request that fetches multiple objects at once.
{
"name": "Luke Skywalker", Although DataLoader is great for that use case, its less helpful when loading data from REST APIs because its primary feature is _batching_, not _caching_. What weve found to be far more important when layering GraphQL over REST APIs is having a resource cache that saves data across multiple GraphQL requests, can be shared across multiple GraphQL servers, and has cache management features like expiry and invalidation that leverage standard HTTP cache control headers.
}
#### Batching
Most REST APIs don't support batching, and if they do, using a batched endpoint may actually jeopardize caching. When you fetch data in a batch request, the response you receive is for the exact combination of resources you're requesting. Unless you request that same combination again, future requests for the same resource won't be served from cache.
Our recommendation is to restrict batching to requests that can't be cached. In those cases, you can actually take advantage of DataLoader as a private implementation detail inside your data source.
```typescript
class PersonalizationAPI extends RESTDataSource {
baseURL = 'https://personalization-api.example.com';
willSendRequest(request: Request) {
request.headers.set('Authorization', this.context.token);
}
private progressLoader = new DataLoader(async (ids: string[]) => {
const progressList = await this.get('progress', {
ids: ids.join(','),
});
return ids.map(id =>
progressList.find((progress: any) => progress.id === id),
);
});
async getProgressFor(id: string) {
return this.progressLoader.load(id);
}
``` ```