From 8be8fe3f829ad9cd0172418e523f35dc704e87a3 Mon Sep 17 00:00:00 2001 From: Rose Koron Date: Tue, 23 Aug 2022 14:35:37 -0700 Subject: [PATCH 01/21] update migration --- docs/source/config.json | 1 + docs/source/migration.mdx | 73 +++++++++++++++++++++------------------ 2 files changed, 40 insertions(+), 34 deletions(-) diff --git a/docs/source/config.json b/docs/source/config.json index 62436491a3d..257ec24b191 100644 --- a/docs/source/config.json +++ b/docs/source/config.json @@ -17,6 +17,7 @@ }, "Fetching Data": { "Resolvers": "/data/resolvers", + "Data sources": "/data/data-sources", "Error handling": "/data/errors", "Subscriptions": "/data/subscriptions" }, diff --git a/docs/source/migration.mdx b/docs/source/migration.mdx index 4df38895c52..8261470cf80 100644 --- a/docs/source/migration.mdx +++ b/docs/source/migration.mdx @@ -4,7 +4,7 @@ title: Migrating to Apollo Server 4 (Alpha) > ⚠️ **Apollo Server 4 is currently in public alpha.** It is not yet feature-complete, and breaking changes might occur between this release and general availability. [Learn about release stages.](/resources/release-stages/#open-source-release-stages) > -> This alpha primarily aims to enable community members to develop integrations between Apollo Server and their favorite web frameworks. Many Apollo Server 3 users **can't upgrade** to this alpha yet. For example, this alpha [removes multiple web framework integrations](#removed-integrations) and doesn't support using [`RESTDataSource`](#datasources). +> This alpha primarily aims to enable community members to develop integrations between Apollo Server and their favorite web frameworks. Many Apollo Server 3 users **can't upgrade** to this alpha yet. For example, this alpha [removes multiple web framework integrations](#removed-integrations). > > We are working on updating our documentation to reflect the changes introduced in Apollo Server 4. This article explains which features _do_ require code changes and how to make them. @@ -359,18 +359,12 @@ For background, Apollo Server uses type system features introduced in v4.7. We w If supporting older versions of TypeScript is important to you and you'd like to help us get `typesVersions` working, we'd appreciate PRs! - - ## Removed constructor options The following `ApolloServer` constructor options have been removed in favor of other features or configuration methods. ### `dataSources` -> ⚠️ This feature is in active development and **does not** currently work as described. -> -> The Apollo Server 4 alpha doesn't support importing from the [`apollo-datasource-rest`](https://www.npmjs.com/package/apollo-datasource-rest) package. We intend to [rename](https://github.com/apollographql/apollo-server/issues/6048) the `apollo-datasource-rest` package to `@apollo/datasource-rest` and update it to be fully compatible with Apollo Server 4. - In Apollo Server 3, the top-level [`dataSources` constructor option](/apollo-server/data/data-sources#adding-data-sources-to-apollo-server) essentially adds a post-processing step to your app's context function, creating `DataSource` subclasses and adding them to a `dataSources` field on your [`context`](/apollo-server/data/resolvers/#the-context-argument) object. This means the TypeScript type the `context` function returns is _different_ from the `context` type your resolvers and plugins receive. Additionally, this design obfuscates that `DataSource` objects are created once per request (i.e., like the rest of the context object). Apollo Server 4 removes the `dataSources` constructor option. You can now treat `DataSources` like any other part of your `context` object. @@ -381,11 +375,11 @@ For example, below, we use the `RESTDataSource` class to create a `DataSource` w -```ts {4, 26-35} title="Apollo Server 3" +```ts title="Apollo Server 3" import { RESTDataSource } from 'apollo-datasource-rest'; import { ApolloServer } from 'apollo-server'; -class MoviesAPI extends RESTDataSource { +class MoviesAPI extends RESTDataSource { //highlight-line baseURL = 'https://movies-api.example.com/'; willSendRequest(request: RequestOptions) { @@ -407,16 +401,18 @@ interface ContextValue { const server = new ApolloServer({ typeDefs, resolvers, - context: ({ req: ExpressRequest }): Omit => { + context: ({ req: ExpressRequest }): Omit => { //highlight-line return { token: getTokenFromRequest(req), }; }, + //highlight-start dataSources: (): ContextValue['dataSources'] => { return { moviesAPI: new MoviesAPI(), }; }, + //highlight-end }); await server.listen(); @@ -424,71 +420,76 @@ await server.listen(); - - -Below is how you write the same code in Apollo Server 4. Note that the `@apollo/datasource-rest` package is now renamed `@apollo/datasource-rest`. - -> ⚠️ This feature is in active development, and the below code snippets **do not** currently work as described. [See above for more details](#datasources). +Below is how you write the same code in Apollo Server 4. -```ts {5, 25-27, 38, 41-43} title="Apollo Server 4" -import { RESTDataSource, RESTDataSourceOptions } from '@apollo/datasource-rest'; +```ts title="Apollo Server 4" +import { RESTDataSource, RequestOptions } from '@apollo/datasource-rest'; +import { KeyValueCache } from '@apollo/utils.keyvaluecache'; import { ApolloServer } from '@apollo/server'; import { startStandaloneServer } from '@apollo/server/standalone'; -class MoviesAPI extends RESTDataSource { +class MoviesAPI extends RESTDataSource { // highlight-line baseURL = 'https://movies-api.example.com/'; private token: string; - constructor(options: { token: string } & RESTDataSourceOptions) { - super(options); // this should send `cache` through + constructor(options: { token: string; cache: KeyValueCache }) { + super(options); // this should send `cache` through this.token = options.token; } willSendRequest(request: RequestOptions) { - request.headers.set('Authorization', this.token); + request.headers['authorization'] = this.token; } - async getMovie(id: string): Movie { + async getMovie(id: string): Promise { return this.get(`movies/${encodeURIComponent(id)}`); } } +// highlight-start interface ContextValue { token: string; dataSources: { moviesAPI: MoviesAPI; - } -}; + }; +} +// highlight-end const server = new ApolloServer({ typeDefs, resolvers, }); -await startStandaloneServer(server, { +const { url } = await startStandaloneServer(server, { context: async ({ req }) => { const token = getTokenFromRequest(req); const { cache } = server; return { token, + //highlight-start dataSources: { moviesAPI: new MoviesAPI({ cache, token }), }, + //highlight-end }; }, }); + ``` +> For Apollo Server 4, `apollo-datasource-rest` package has been updated and renamed [`@apollo/datasource-rest`](https://www.npmjs.com/package/@apollo/datasource-rest). + If you want to access your entire context's value within your `DataSource`, you can do so by making your context value a `class` (enabling it to refer to itself via `this` in its constructor): -```ts {24-36, 38, 43} -import { RESTDataSource, RESTDataSourceOptions } from '@apollo/datasource-rest'; +```ts +import { RESTDataSource, RequestOptions } from '@apollo/datasource-rest'; //highlight-line +import { KeyValueCache } from '@apollo/utils.keyvaluecache'; import { ApolloServer } from '@apollo/server'; import { startStandaloneServer } from '@apollo/server/standalone'; import { IncomingMessage } from 'http'; @@ -497,26 +498,27 @@ class MoviesAPI extends RESTDataSource { baseURL = 'https://movies-api.example.com/'; private contextValue: ContextValue; - constructor(options: { contextValue: ContextValue } & RESTDataSourceOptions) { - super(options); // this should send `cache` through + constructor(options: { contextValue: ContextValue; cache: KeyValueCache }) { + super(options); // this should send `cache` through this.contextValue = options.contextValue; } willSendRequest(request: RequestOptions) { - request.headers.set('Authorization', this.contextValue.token); + request.headers['authorization'] = this.contextValue.token; } - async getMovie(id: string): Movie { + async getMovie(id: string): Promise { return this.get(`movies/${encodeURIComponent(id)}`); } } +// highlight-start class ContextValue { public token: string; public dataSources: { moviesAPI: MoviesAPI; - } - constructor({ req, server }: { req: IncomingMessage, server: ApolloServer }) { + }; + constructor({ req, server }: { req: IncomingMessage; server: ApolloServer }) { this.token = getTokenFromRequest(req); const { cache } = server; this.dataSources = { @@ -524,13 +526,15 @@ class ContextValue { }; } } +// highlight-end const server = new ApolloServer({ typeDefs, resolvers, }); + await startStandaloneServer(server, { - context: async ({ req }) => new ContextValue({ req, server }), + context: async ({ req }) => new ContextValue({ req, server }), //highlight-line }); ``` @@ -1611,6 +1615,7 @@ new ApolloServer({ ## Renamed packages The following packages have been renamed in Apollo Server 4: + * `apollo-datasource-rest` is now [`@apollo/datasource-rest`](https://www.npmjs.com/package/@apollo/datasource-rest). The `@apollo/datasource-rest` package is compatible with both Apollo Server 3 and 4. * `apollo-server-plugin-response-cache` is now [`@apollo/server-plugin-response-cache`](https://www.npmjs.com/package/@apollo/server-plugin-response-cache). * `apollo-server-plugin-operation-registry` is now [`@apollo/server-plugin-operation-registry`](https://www.npmjs.com/package/@apollo/server-plugin-operation-registry). * `apollo-reporting-protobuf` (an internal implementation detail for the usage reporting plugin) is now [`@apollo/usage-reporting-protobuf`](https://www.npmjs.com/package/@apollo/usage-reporting-protobuf). From e93413f3c8f94415f01509433ebbca72d3bdc23c Mon Sep 17 00:00:00 2001 From: Rose Koron Date: Tue, 23 Aug 2022 15:49:09 -0700 Subject: [PATCH 02/21] Start data sources article --- docs/source/data/data-sources.mdx | 4 +--- docs/source/migration.mdx | 3 ++- main-branch | 1 + 3 files changed, 4 insertions(+), 4 deletions(-) create mode 160000 main-branch diff --git a/docs/source/data/data-sources.mdx b/docs/source/data/data-sources.mdx index 00667ac7d80..1462a0ef840 100644 --- a/docs/source/data/data-sources.mdx +++ b/docs/source/data/data-sources.mdx @@ -1,10 +1,8 @@ --- -title: Data sources +title: Fetching from REST description: Manage connections to databases and REST APIs --- - - **Data sources** are classes that Apollo Server can use to encapsulate fetching data from a particular source, such as a database or a REST API. These classes help handle caching, deduplication, and errors while resolving operations. Your server can use any number of different data sources. You don't _have_ to use data sources to fetch data, but they're strongly recommended. diff --git a/docs/source/migration.mdx b/docs/source/migration.mdx index 8261470cf80..e1f68b6141d 100644 --- a/docs/source/migration.mdx +++ b/docs/source/migration.mdx @@ -426,6 +426,7 @@ Below is how you write the same code in Apollo Server 4. ```ts title="Apollo Server 4" import { RESTDataSource, RequestOptions } from '@apollo/datasource-rest'; +// KeyValueCache is the type of Apollo server's default cache import { KeyValueCache } from '@apollo/utils.keyvaluecache'; import { ApolloServer } from '@apollo/server'; import { startStandaloneServer } from '@apollo/server/standalone'; @@ -435,7 +436,7 @@ class MoviesAPI extends RESTDataSource { // highlight-line private token: string; constructor(options: { token: string; cache: KeyValueCache }) { - super(options); // this should send `cache` through + super(options); // this should send our server's `cache` through this.token = options.token; } diff --git a/main-branch b/main-branch new file mode 160000 index 00000000000..9389da78556 --- /dev/null +++ b/main-branch @@ -0,0 +1 @@ +Subproject commit 9389da785567a56e989430962564afc71e93bd7f From 8aeff399ebde448ee8647abebc3a876ee477519e Mon Sep 17 00:00:00 2001 From: Rose Koron Date: Tue, 23 Aug 2022 16:27:18 -0700 Subject: [PATCH 03/21] Start fixing links --- docs/source/data/data-sources.mdx | 49 +++++++++++++++++++++---------- docs/source/data/resolvers.mdx | 12 ++------ docs/source/migration.mdx | 1 + 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/docs/source/data/data-sources.mdx b/docs/source/data/data-sources.mdx index 1462a0ef840..2c873542c83 100644 --- a/docs/source/data/data-sources.mdx +++ b/docs/source/data/data-sources.mdx @@ -45,25 +45,44 @@ If none of these implementations applies to your use case, you can create your o ## Adding data sources to Apollo Server -You provide your `DataSource` subclasses to the `ApolloServer` constructor, like so: +You provide your `DataSource` subclasses to the `context` initialization function, like so: + +```ts title="index.ts" +//highlight-start +interface ContextValue { + token: string; + dataSources: { + moviesAPI: MoviesAPI; + personalizationAPI: PersonalizationAPI; + }; +} +//highlight-end -```ts {4-9} title="index.js" -const server = new ApolloServer({ +const server = new ApolloServer({ typeDefs, resolvers, - csrfPrevention: true, - cache: 'bounded', - plugins: [ApolloServerPluginLandingPageLocalDefault({ embed: true })], - dataSources: () => { +}); + +const { url } = await startStandaloneServer(server, { + context: async ({ req }) => { + const token = getTokenFromRequest(req); + const { cache } = server; return { - moviesAPI: new MoviesAPI(), - personalizationAPI: new PersonalizationAPI(), + token, + //highlight-start + dataSources: { + moviesAPI: new MoviesAPI(), + personalizationAPI: new PersonalizationAPI(), + }, + //highlight-end }; }, }); + +console.log(`🚀 Server ready at ${url}`); ``` -- As shown, the `dataSources` option is a _function_. This function returns an _object_ containing instances of your `DataSource` subclasses (in this case, `MoviesAPI` and `PersonalizationAPI`). +- As shown, the `context` option is a _function_. This function returns an _object_ containing instances of your `DataSource` subclasses (in this case, `MoviesAPI` and `PersonalizationAPI`). - Apollo Server calls this function for _every incoming operation_. It automatically assigns the returned object to the `dataSources` field of [the `context` object](./resolvers/#the-context-argument) that's passed between your server's resolvers. - Also as shown, **the function should create a new instance of each data source for each operation.** If multiple operations share a single data source instance, you might accidentally combine results from multiple operations. @@ -150,7 +169,7 @@ An example of each is shown below: -```ts +```ts class MoviesAPI extends RESTDataSource { constructor() { super(); @@ -227,7 +246,7 @@ Data sources also have access to the GraphQL operation context, which is useful #### Setting a header -```ts +```ts class PersonalizationAPI extends RESTDataSource { willSendRequest(request) { request.headers.set('Authorization', this.context.token); @@ -237,7 +256,7 @@ class PersonalizationAPI extends RESTDataSource { #### Adding a query parameter -```ts +```ts class PersonalizationAPI extends RESTDataSource { willSendRequest(request) { request.params.set('api_key', this.context.token); @@ -265,7 +284,7 @@ class PersonalizationAPI extends RESTDataSource { In some cases, you'll want to set the URL based on the environment or other contextual values. To do this, you can override `resolveURL`: -```ts +```ts async resolveURL(request: RequestOptions) { if (!this.baseURL) { const addresses = await resolveSrv(request.path.split("/")[1] + ".service.consul"); @@ -293,7 +312,7 @@ Most REST APIs don't support batching. When they do, using a batched endpoint ca We recommend that you restrict batching to requests that _can't_ be cached. In these cases, you can take advantage of DataLoader as a private implementation detail inside your `RESTDataSource`: -```ts +```ts class PersonalizationAPI extends RESTDataSource { constructor() { super(); diff --git a/docs/source/data/resolvers.mdx b/docs/source/data/resolvers.mdx index a3cbfc5dbe0..4e0d7d25c95 100644 --- a/docs/source/data/resolvers.mdx +++ b/docs/source/data/resolvers.mdx @@ -77,12 +77,8 @@ const users = [ ]; ``` - - Now we can define a resolver for the `user` field, like so: ```ts @@ -385,11 +381,7 @@ Resolver functions are passed four arguments: `parent`, `args`, `context`, and ` > ⚠️ The Apollo Server 4 alpha doesn't currently support using [`RESTDataSource`](../migration#datasources), a class commonly used to fetch data from a database or a REST API. This feature is in active development, so check back frequently for updates. -The `context` argument is useful for passing things that any resolver might need, like [authentication scope](https://blog.apollographql.com/authorization-in-graphql-452b1c402a9), database connections, and custom fetch functions. - - +The `context` argument is useful for passing things that any resolver might need, like [authentication scope](https://blog.apollographql.com/authorization-in-graphql-452b1c402a9), database connections, and custom fetch functions. If you're using [dataloaders to batch requests](./data-sources/#using-with-dataloader) across resolvers, you can attach them to the `context` as well. **Resolvers should never destructively modify the `context` argument.** This ensures consistency across all resolvers and prevents unexpected errors. @@ -407,7 +399,7 @@ const resolvers = { // Example resolver adminExample: (parent, args, context, info) => { if (context.authScope !== ADMIN) { - throw new GraphQLError('not admin!', { + throw new GraphQLError('not admin!', { extensions: { code: 'UNAUTHENTICATED' } }); } diff --git a/docs/source/migration.mdx b/docs/source/migration.mdx index e1f68b6141d..a1a77db8ea1 100644 --- a/docs/source/migration.mdx +++ b/docs/source/migration.mdx @@ -478,6 +478,7 @@ const { url } = await startStandaloneServer(server, { }, }); +console.log(`🚀 Server ready at ${url}`); ``` From 60fade8730fab641e362e8e114f9e126718b169f Mon Sep 17 00:00:00 2001 From: Rose Koron Date: Wed, 24 Aug 2022 15:01:38 -0700 Subject: [PATCH 04/21] Progress so far --- docs/source/config.json | 3 +- docs/source/data/fetching-data.mdx | 164 +++++++++++ .../{data-sources.mdx => fetching-rest.mdx} | 268 +++++++++++------- 3 files changed, 328 insertions(+), 107 deletions(-) create mode 100644 docs/source/data/fetching-data.mdx rename docs/source/data/{data-sources.mdx => fetching-rest.mdx} (54%) diff --git a/docs/source/config.json b/docs/source/config.json index 257ec24b191..2c90486fc4b 100644 --- a/docs/source/config.json +++ b/docs/source/config.json @@ -17,7 +17,8 @@ }, "Fetching Data": { "Resolvers": "/data/resolvers", - "Data sources": "/data/data-sources", + "Fetching data": "/data/fetching-data", + "Fetching from REST": "/data/fetching-rest", "Error handling": "/data/errors", "Subscriptions": "/data/subscriptions" }, diff --git a/docs/source/data/fetching-data.mdx b/docs/source/data/fetching-data.mdx new file mode 100644 index 00000000000..c3a007689ce --- /dev/null +++ b/docs/source/data/fetching-data.mdx @@ -0,0 +1,164 @@ +--- +title: Fetching Data +description: Using RESTDataSource to fetch data from REST APIs +--- + + + +**Data sources** are classes that Apollo Server can use to encapsulate fetching data from a particular source, such as a database or a REST API. These classes help handle caching, deduplication, and errors while resolving operations. + +Your server can use any number of different data sources. You don't _have_ to use data sources to fetch data, but they're strongly recommended. + +```mermaid +flowchart LR; + restAPI(REST API); + db(Database); + subgraph ApolloServer; + restDataSource(RESTDataSource); + DBDataSource(DBDataSource); + end + restDataSource --Fetches data--> restAPI; + DBDataSource --Fetches data--> db; + client(ApolloClient); + client --Sends query--> ApolloServer; + class restAPI,db secondary; +``` + + + + +## Open-source implementations + +All data source implementations extend the generic [`DataSource` abstract class](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-datasource/src/index.ts), which is included in the `apollo-datasource` package. Subclasses of a `DataSource` should define whatever logic is required to communicate with a particular store or API. + +Apollo and the larger community maintain the following open-source implementations: + +> Do you maintain a `DataSource` implementation that isn't listed here? Please [submit a PR](https://github.com/apollographql/apollo-server/blob/main/docs/source/data/data-sources.md) to be added to the list! + +| Class | Source | For Use With | +|------------------|-----------|-----------------------| +| [`RESTDataSource`](https://github.com/apollographql/apollo-server/tree/main/packages/apollo-datasource-rest) | Apollo | REST APIs ([see below](#restdatasource-reference)) | +| [`HTTPDataSource`](https://github.com/StarpTech/apollo-datasource-http) | Community | HTTP/REST APIs (newer community alternative to `RESTDataSource`) | +| [`SQLDataSource`](https://github.com/cvburgess/SQLDataSource) | Community | SQL databases (via [Knex.js](http://knexjs.org/)) | +| [`MongoDataSource`](https://github.com/GraphQLGuide/apollo-datasource-mongodb/) | Community | MongoDB | +| [`CosmosDataSource`](https://github.com/andrejpk/apollo-datasource-cosmosdb) | Community | Azure Cosmos DB | +| [`FirestoreDataSource`](https://github.com/swantzter/apollo-datasource-firestore) | Community | Cloud Firestore | + +If none of these implementations applies to your use case, you can create your own custom `DataSource` subclass. + +> 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. --> + +## Adding data sources to Apollo Server's context + +You provide your `DataSource` subclasses to the `context` initialization function, like so: + + + +```ts title="index.ts" +//highlight-start +interface ContextValue { + dataSources: { + moviesAPI: MoviesAPI; + personalizationAPI: PersonalizationAPI; + }; +} +//highlight-end + +const server = new ApolloServer({ + typeDefs, + resolvers, +}); + +const { url } = await startStandaloneServer(server, { + context: async ({ req }) => { + return { + //highlight-start + // We create new instances of our data sources with each request + dataSources: { + moviesAPI: new MoviesAPI(), + personalizationAPI: new PersonalizationAPI(), + }, + //highlight-end + }; + }, +}); + +console.log(`🚀 Server ready at ${url}`); +``` + + + + +Apollo Server calls the [the `context` initialization](../resolvers/#the-context-argument) function for _every incoming operation_. This means: +- As shown above, with every operation `context` returns an _object_ containing new instances of your `DataSource` subclasses (in this case, `MoviesAPI` and `PersonalizationAPI`). +- These subclasses are accessible from the [the `context` value argument](./resolvers/#the-context-argument) that's passed between your server's resolvers. +- The **`context` function should create a new instance of each data source for each operation.** If multiple operations share a single data source instance, you might accidentally combine results from multiple operations. + +Your resolvers can now access your data sources from the shared `context` object and use them to fetch data: + +```ts title="resolvers.ts" +const resolvers = { + Query: { + movie: async (_, { id }, { dataSources }) => { + return dataSources.moviesAPI.getMovie(id); + }, + mostViewedMovies: async (_, __, { dataSources }) => { + return dataSources.moviesAPI.getMostViewedMovies(); + }, + favorites: async (_, __, { dataSources }) => { + return dataSources.personalizationAPI.getFavorites(); + }, + }, +}; +``` + +## Caching + +By default, data source implementations use Apollo Server's in-memory cache to store the results of past fetches. + +When you initialize Apollo Server, you can provide its constructor a _different_ cache object that implements the [`KeyValueCache` interface](https://github.com/apollographql/apollo-utils/tree/main/packages/keyValueCache#keyvaluecache-interface). This enables you to back your cache with shared stores like Memcached or Redis. + +```ts title="server.ts" +const server = new ApolloServer({ + typeDefs, + resolvers, + cache: new MyCustomKeyValueCache() +}); +``` + +### Using an external cache backend + +When running multiple instances of your server, you should use a shared cache backend. This enables one server instance to use the cached result from _another_ instance. + +Apollo Server supports using [Memcached](https://memcached.org/), [Redis](https://redis.io/), or other cache backends via the [`keyv`](https://www.npmjs.com/package/keyv) package. For examples, see [Configuring external caching](../performance/cache-backends#configuring-external-caching). + +You can also choose to implement your own cache backend. For more information, see [Implementing your own cache backend](../performance/cache-backends#implementing-your-own-cache-backend). + diff --git a/docs/source/data/data-sources.mdx b/docs/source/data/fetching-rest.mdx similarity index 54% rename from docs/source/data/data-sources.mdx rename to docs/source/data/fetching-rest.mdx index 2c873542c83..815e26e7a59 100644 --- a/docs/source/data/data-sources.mdx +++ b/docs/source/data/fetching-rest.mdx @@ -1,56 +1,74 @@ --- title: Fetching from REST -description: Manage connections to databases and REST APIs +description: Using RESTDataSource to fetch data from REST APIs --- -**Data sources** are classes that Apollo Server can use to encapsulate fetching data from a particular source, such as a database or a REST API. These classes help handle caching, deduplication, and errors while resolving operations. - -Your server can use any number of different data sources. You don't _have_ to use data sources to fetch data, but they're strongly recommended. +The `RESTDataSource` class helps you fetch data from REST APIs. The `RESTDataSource` class helps handle caching, deduplication, and errors while resolving operations. ```mermaid flowchart LR; restAPI(REST API); - sql(SQL Database); subgraph ApolloServer; restDataSource(RESTDataSource); - sqlDataSource(SQLDataSource); end restDataSource --Fetches data--> restAPI; - sqlDataSource --Fetches data--> sql; client(ApolloClient); client --Sends query--> ApolloServer; - class restAPI,sql secondary; ``` -## Open-source implementations +> For more information about fetching from data sources other than a REST API, see [Fetching Data](./fetching-data). + +See the [`@apollo/datasource-rest` page](https://www.npmjs.com/package/@apollo/datasource-rest) for the full details of the `RESTDataSource` API. + +## Creating subclasses + +To get started, install the [`@apollo/datasource-rest` package](https://www.npmjs.com/package/@apollo/datasource-rest): + +```bash +npm install @apollo/datasource-rest +``` + +Your server should define a separate subclass of `RESTDataSource` for each REST API it communicates with. Here's an example of a `RESTDataSource` subclass that defines two data-fetching methods, `getMovie` and `getMostViewedMovies`: + -All data source implementations extend the generic [`DataSource` abstract class](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-datasource/src/index.ts), which is included in the `apollo-datasource` package. Subclasses of a `DataSource` should define whatever logic is required to communicate with a particular store or API. +```ts title="movies-api.ts" +import { RESTDataSource } from '@apollo/datasource-rest'; -Apollo and the larger community maintain the following open-source implementations: +class MoviesAPI extends RESTDataSource { + baseURL = 'https://movies-api.example.com/'; + constructor() { + super(); + } + + async getMovie(id):Promise { + return this.get(`movies/${encodeURIComponent(id)}`); + } -> Do you maintain a `DataSource` implementation that isn't listed here? Please [submit a PR](https://github.com/apollographql/apollo-server/blob/main/docs/source/data/data-sources.md) to be added to the list! + async getMostViewedMovies(limit = '10'):Promise { + const data = await this.get('movies', { + params: { + per_page: limit, + order_by: 'most_viewed', + }, + }); + return data.results; + } +} +``` -| Class | Source | For Use With | -|------------------|-----------|-----------------------| -| [`RESTDataSource`](https://github.com/apollographql/apollo-server/tree/main/packages/apollo-datasource-rest) | Apollo | REST APIs ([see below](#restdatasource-reference)) | -| [`HTTPDataSource`](https://github.com/StarpTech/apollo-datasource-http) | Community | HTTP/REST APIs (newer community alternative to `RESTDataSource`) | -| [`SQLDataSource`](https://github.com/cvburgess/SQLDataSource) | Community | SQL databases (via [Knex.js](http://knexjs.org/)) | -| [`MongoDataSource`](https://github.com/GraphQLGuide/apollo-datasource-mongodb/) | Community | MongoDB | -| [`CosmosDataSource`](https://github.com/andrejpk/apollo-datasource-cosmosdb) | Community | Azure Cosmos DB | -| [`FirestoreDataSource`](https://github.com/swantzter/apollo-datasource-firestore) | Community | Cloud Firestore | + -If none of these implementations applies to your use case, you can create your own custom `DataSource` subclass. +You can extend the `RESTDataSource` class to implement whatever data-fetching methods your resolvers need. These methods can use built-in convenience methods (like `get` and `post`) to perform HTTP requests, helping you add query parameters, parse JSON results, and handle errors. -> 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. +## Adding subclasses to Apollo Server's context -## Adding data sources to Apollo Server +You can then provide your `RESTDataSource` subclasses to the `context` initialization function, like so: -You provide your `DataSource` subclasses to the `context` initialization function, like so: + ```ts title="index.ts" //highlight-start interface ContextValue { - token: string; dataSources: { moviesAPI: MoviesAPI; personalizationAPI: PersonalizationAPI; @@ -65,11 +83,9 @@ const server = new ApolloServer({ const { url } = await startStandaloneServer(server, { context: async ({ req }) => { - const token = getTokenFromRequest(req); - const { cache } = server; return { - token, //highlight-start + // We create new instances of our data sources with each request dataSources: { moviesAPI: new MoviesAPI(), personalizationAPI: new PersonalizationAPI(), @@ -82,13 +98,17 @@ const { url } = await startStandaloneServer(server, { console.log(`🚀 Server ready at ${url}`); ``` -- As shown, the `context` option is a _function_. This function returns an _object_ containing instances of your `DataSource` subclasses (in this case, `MoviesAPI` and `PersonalizationAPI`). -- Apollo Server calls this function for _every incoming operation_. It automatically assigns the returned object to the `dataSources` field of [the `context` object](./resolvers/#the-context-argument) that's passed between your server's resolvers. -- Also as shown, **the function should create a new instance of each data source for each operation.** If multiple operations share a single data source instance, you might accidentally combine results from multiple operations. + + + +Apollo Server calls the [the `context` initialization](../resolvers/#the-context-argument) function for _every incoming operation_. This means: +- As shown above, with every operation `context` returns an _object_ containing new instances of your `RESTDataSource` subclasses (in this case, `MoviesAPI` and `PersonalizationAPI`). +- These subclasses are accessible from the [the `context` argument](./resolvers/#the-context-argument) that's passed between your server's resolvers. +- The **`context` function should create a new instance of each `RESTDataSource` subclass for each operation.** If multiple operations share a single data source instance, you might accidentally combine results from multiple operations. Your resolvers can now access your data sources from the shared `context` object and use them to fetch data: -```ts title="resolvers.js" +```ts title="resolvers.ts" const resolvers = { Query: { movie: async (_, { id }, { dataSources }) => { @@ -106,64 +126,80 @@ const resolvers = { ## Caching -By default, data source implementations use Apollo Server's in-memory cache to store the results of past fetches. +> 📣 **New in Apollo Server 4**: Apollo Server no longer automatically provides it's cache to data sources. [See here for more details](#datasources). -When you initialize Apollo Server, you can provide its constructor a _different_ cache object that implements the [`KeyValueCache` interface](https://github.com/apollographql/apollo-utils/tree/main/packages/keyValueCache#keyvaluecache-interface). This enables you to back your cache with shared stores like Memcached or Redis. +You can pass your `RESTDataSource` subclasses a cache (such as Apollo Server's default cache) to store the results of past fetches: -### Using an external cache backend + -When running multiple instances of your server, you should use a shared cache backend. This enables one server instance to use the cached result from _another_ instance. +```ts +import { RESTDataSource } from '@apollo/datasource-rest'; +// KeyValueCache is the type of Apollo server's default cache +import { KeyValueCache } from '@apollo/utils.keyvaluecache'; +import { ApolloServer } from '@apollo/server'; +import { startStandaloneServer } from '@apollo/server/standalone'; -Apollo Server supports using [Memcached](https://memcached.org/), [Redis](https://redis.io/), or other cache backends via the [`keyv`](https://www.npmjs.com/package/keyv) package. For examples, see [Configuring external caching](../performance/cache-backends#configuring-external-caching). +class MoviesAPI extends RESTDataSource { + baseURL = 'https://movies-api.example.com/'; -You can also choose to implement your own cache backend. For more information, see [Implementing your own cache backend](../performance/cache-backends#implementing-your-own-cache-backend). + //highlight-start + constructor(options: {cache: KeyValueCache }) { + super(options); // this should send our server's `cache` through + } + //highlight-end -## `RESTDataSource` reference + // data fetching methods, etc. +} -The `RESTDataSource` abstract class helps you fetch data from REST APIs. Your server defines a separate subclass of `RESTDataSource` for each REST API it communicates with. +interface ContextValue { + dataSources: { + moviesAPI: MoviesAPI; + }; +} -To get started, install the `apollo-datasource-rest` package: +const server = new ApolloServer({ + typeDefs, + resolvers, +}); -```bash -npm install apollo-datasource-rest +const { url } = await startStandaloneServer(server, { + context: async ({ req }) => { + const { cache } = server; + return { + token, + //highlight-start + dataSources: { + moviesAPI: new MoviesAPI({ cache, token }), + }, + //highlight-end + }; + }, +}); ``` -You then extend the `RESTDataSource` class and implement whatever data-fetching methods your resolvers need. These methods can use built-in convenience methods (like `get` and `post`) to perform HTTP requests, helping you add query parameters, parse JSON results, and handle errors. + -### Example +If you want Apollo Server to use another cache backend, you can provide its constructor a _different_ cache object that implements the [`KeyValueCache` interface](https://github.com/apollographql/apollo-utils/tree/main/packages/keyValueCache#keyvaluecache-interface): -Here's an example `RESTDataSource` subclass that defines two data-fetching methods, `getMovie` and `getMostViewedMovies`: +```ts title="server.ts" +const server = new ApolloServer({ + typeDefs, + resolvers, + cache: new MyCustomKeyValueCache() +}); +``` -```ts title="movies-api.js" -const { RESTDataSource } = require('apollo-datasource-rest'); +### Using an external cache backend -class MoviesAPI extends RESTDataSource { - constructor() { - // Always call super() - super(); - // Sets the base URL for the REST API - this.baseURL = 'https://movies-api.example.com/'; - } +When running multiple instances of your server, you should use a shared cache backend. This enables one server instance to use the cached result from _another_ instance. - async getMovie(id) { - // Send a GET request to the specified endpoint - return this.get(`movies/${encodeURIComponent(id)}`); - } +Apollo Server supports using [Memcached](https://memcached.org/), [Redis](https://redis.io/), or other cache backends via the [`keyv`](https://www.npmjs.com/package/keyv) package. For examples, see [Configuring external caching](../performance/cache-backends#configuring-external-caching). - async getMostViewedMovies(limit = 10) { - const data = await this.get('movies', { - // Query parameters - per_page: limit, - order_by: 'most_viewed', - }); - return data.results; - } -} -``` +You can also choose to implement your own cache backend. For more information, see [Implementing your own cache backend](../performance/cache-backends#implementing-your-own-cache-backend). -### HTTP Methods +## HTTP Methods -`RESTDataSource` includes convenience methods for common REST API request methods: `get`, `post`, `put`, `patch`, and `delete` ([see the source](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-datasource-rest/src/RESTDataSource.ts#L154-L202)). +`RESTDataSource` includes convenience methods for common REST API request methods: `get`, `post`, `put`, `patch`, and `delete` ([see the source](https://github.com/apollographql/datasource-rest/blob/main/src/RESTDataSource.ts#L163)). An example of each is shown below: @@ -171,10 +207,7 @@ An example of each is shown below: ```ts class MoviesAPI extends RESTDataSource { - constructor() { - super(); - this.baseURL = 'https://movies-api.example.com/'; - } + baseURL = 'https://movies-api.example.com/'; // GET async getMovie(id) { @@ -203,7 +236,7 @@ class MoviesAPI extends RESTDataSource { async updateMovie(movie) { return this.patch( `movies`, // path - { id: movie.id, movie }, // request body + { body: { id: movie.id, movie } }, // request body ); } @@ -214,11 +247,12 @@ class MoviesAPI extends RESTDataSource { ); } } + ``` -> Note the use of [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent). This is a standard JavaScript function that encodes special characters in a URI, preventing a possible injection attack vector. +> Note the use of [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent). This is a standard function that encodes special characters in a URI, preventing a possible injection attack vector. For a simple example, suppose our REST endpoint responded to the following URLs: @@ -227,74 +261,92 @@ For a simple example, suppose our REST endpoint responded to the following URLs: A "malicious" client could provide an `:id` of `1/characters` to target the delete `characters` endpoint when it was the singular `movie` endpoint that we were trying to delete. URI encoding prevents this kind of injection by transforming the `/` into `%2F`. This can then be correctly decoded and interpreted by the server and won't be treated as a path segment. -#### Method parameters +### Method parameters For all HTTP convenience methods, the **first parameter** is the relative path of the endpoint you're sending the request to (e.g., `movies`). The **second parameter** depends on the HTTP method: -- For HTTP methods with a request body (`post`, `put`, `patch`), the second parameter _is_ the request body. +- For HTTP methods with a request body (`post`, `put`, `patch`), the second parameter _is_ the request body (e.g., `{ body: { id: movie.id } }`). - For HTTP methods _without_ a request body, the second parameter is an object with keys and values corresponding to the request's query parameters. For all methods, the **third parameter** is an `init` object that enables you to provide additional options (such as headers and referrers) to the `fetch` API that's used to send the request. For details, [see MDN's fetch docs](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters). -### Intercepting fetches +## Intercepting fetches `RESTDataSource` includes a `willSendRequest` method that you can override to modify outgoing requests before they're sent. For example, you can use this method to add headers or query parameters. This method is most commonly used for authorization or other concerns that apply to all sent requests. Data sources also have access to the GraphQL operation context, which is useful for storing a user token or other relevant information. -#### Setting a header +### Setting a header + + ```ts +import { RESTDataSource, WillSendRequestOptions } from '@apollo/datasource-rest'; + class PersonalizationAPI extends RESTDataSource { - willSendRequest(request) { - request.headers.set('Authorization', this.context.token); + override baseURL = 'https://personalization-api.example.com/'; + + constructor(private token: string) { + super(); + } + + override willSendRequest(request: WillSendRequestOptions) { + request.headers['authorization'] = this.token; } } ``` -#### Adding a query parameter + + +> If you're using TypeScript, make sure to import the `WillSendRequestOptions` type. + +### Adding a query parameter + + ```ts +import { RESTDataSource } from '@apollo/datasource-rest'; + class PersonalizationAPI extends RESTDataSource { willSendRequest(request) { - request.params.set('api_key', this.context.token); + request.params.set('api_key', this.token); } } ``` -#### Using with TypeScript + -If you're using TypeScript, make sure to import the `RequestOptions` type: +## Resolving URLs dynamically + +In some cases, you'll want to set the URL based on the environment or other contextual values. To do this, you can override `resolveURL`: -```typescript -import { RESTDataSource, RequestOptions } from 'apollo-datasource-rest'; + + +```ts +import { RESTDataSource, RequestOptions } from '@apollo/datasource-rest'; class PersonalizationAPI extends RESTDataSource { - baseURL = 'https://personalization-api.example.com/'; + constructor(private token: string) { + super(); + } - willSendRequest(request: RequestOptions) { - request.headers.set('Authorization', this.context.token); + override async resolveURL(path: string, request: RequestOptions) { + if (!this.baseURL) { + const addresses = await resolveSrv(path.split('/')[1] + '.service.consul'); + this.baseURL = addresses[0]; + } + return super.resolveURL(path, request); } } + ``` -### Resolving URLs dynamically + -In some cases, you'll want to set the URL based on the environment or other contextual values. To do this, you can override `resolveURL`: -```ts -async resolveURL(request: RequestOptions) { - if (!this.baseURL) { - const addresses = await resolveSrv(request.path.split("/")[1] + ".service.consul"); - this.baseURL = addresses[0]; - } - return super.resolveURL(request); -} -``` - -### Using with DataLoader +## Using with DataLoader The [DataLoader](https://github.com/graphql/dataloader) utility was designed for a specific use case: 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. It also combines loads that occur during a single tick of the event loop into a batched request that fetches multiple objects at once. @@ -306,7 +358,7 @@ When layering GraphQL over REST APIs, it's most helpful to have a resource cache - Can be shared across multiple GraphQL servers - Provides cache management features like expiry and invalidation that use standard HTTP cache control headers -#### Batching with REST APIs +### Batching with REST APIs Most REST APIs don't support batching. When they do, using a batched endpoint can _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. @@ -335,4 +387,8 @@ class PersonalizationAPI extends RESTDataSource { async getProgressFor(id) { return this.progressLoader.load(id); } +} ``` + + + From 6dc50f5b5c7cdff6b01d5d351998caab38469241 Mon Sep 17 00:00:00 2001 From: Rose Koron Date: Wed, 24 Aug 2022 15:05:01 -0700 Subject: [PATCH 05/21] Remove accidental submodules --- main-branch | 1 - 1 file changed, 1 deletion(-) delete mode 160000 main-branch diff --git a/main-branch b/main-branch deleted file mode 160000 index 9389da78556..00000000000 --- a/main-branch +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9389da785567a56e989430962564afc71e93bd7f From b4bcb1d2a6ce2bda6191c041551386981d8d5cba Mon Sep 17 00:00:00 2001 From: Rose Koron Date: Thu, 25 Aug 2022 10:33:15 -0700 Subject: [PATCH 06/21] Add Trevor feedback --- docs/source/data/fetching-data.mdx | 25 +++-- docs/source/data/fetching-rest.mdx | 171 +++++++++++++++++------------ docs/source/migration.mdx | 1 + 3 files changed, 113 insertions(+), 84 deletions(-) diff --git a/docs/source/data/fetching-data.mdx b/docs/source/data/fetching-data.mdx index c3a007689ce..20417d2b650 100644 --- a/docs/source/data/fetching-data.mdx +++ b/docs/source/data/fetching-data.mdx @@ -3,18 +3,9 @@ title: Fetching Data description: Using RESTDataSource to fetch data from REST APIs --- - -**Data sources** are classes that Apollo Server can use to encapsulate fetching data from a particular source, such as a database or a REST API. These classes help handle caching, deduplication, and errors while resolving operations. +Apollo Server can use to encapsulate fetching data from a particular source, such as a database or a REST API. These classes help handle caching, deduplication, and errors while resolving operations. Your server can use any number of different data sources. You don't _have_ to use data sources to fetch data, but they're strongly recommended. @@ -57,6 +48,14 @@ Do we want to tell people how to create data sources themselves? Is there an exa ## Open-source implementations +> **New in Apollo Server 4**: if you are using an open-source data source implementation from Apollo Server 3 expand the below example to see how to make it work in Apollo Server 4. + + + All data source implementations extend the generic [`DataSource` abstract class](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-datasource/src/index.ts), which is included in the `apollo-datasource` package. Subclasses of a `DataSource` should define whatever logic is required to communicate with a particular store or API. Apollo and the larger community maintain the following open-source implementations: @@ -76,6 +75,8 @@ If none of these implementations applies to your use case, you can create your o > 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. --> + + ## Adding data sources to Apollo Server's context You provide your `DataSource` subclasses to the `context` initialization function, like so: diff --git a/docs/source/data/fetching-rest.mdx b/docs/source/data/fetching-rest.mdx index 815e26e7a59..443400479c7 100644 --- a/docs/source/data/fetching-rest.mdx +++ b/docs/source/data/fetching-rest.mdx @@ -35,13 +35,10 @@ Your server should define a separate subclass of `RESTDataSource` for each REST import { RESTDataSource } from '@apollo/datasource-rest'; class MoviesAPI extends RESTDataSource { - baseURL = 'https://movies-api.example.com/'; - constructor() { - super(); - } + override baseURL = 'https://movies-api.example.com/'; async getMovie(id):Promise { - return this.get(`movies/${encodeURIComponent(id)}`); + return this.get(`movies/${encodeURIComponent(id)}`); } async getMostViewedMovies(limit = '10'):Promise { @@ -58,9 +55,9 @@ class MoviesAPI extends RESTDataSource { -You can extend the `RESTDataSource` class to implement whatever data-fetching methods your resolvers need. These methods can use built-in convenience methods (like `get` and `post`) to perform HTTP requests, helping you add query parameters, parse JSON results, and handle errors. +You can extend the `RESTDataSource` class to implement whatever data-fetching methods your resolvers need. These methods should use the built-in convenience methods (e.g., `get` and `post`) to perform HTTP requests, helping you add query parameters, parse and cache JSON results, dedupe requests, and handle errors. -## Adding subclasses to Apollo Server's context +## Adding data sources to Apollo Server's context You can then provide your `RESTDataSource` subclasses to the `context` initialization function, like so: @@ -82,13 +79,15 @@ const server = new ApolloServer({ }); const { url } = await startStandaloneServer(server, { - context: async ({ req }) => { + context: async () => { + const { cache } = server; // highlight-line return { //highlight-start - // We create new instances of our data sources with each request + // We create new instances of our data sources with each request, + // passing in our server's cache. dataSources: { - moviesAPI: new MoviesAPI(), - personalizationAPI: new PersonalizationAPI(), + moviesAPI: new MoviesAPI({ cache }), + personalizationAPI: new PersonalizationAPI({ cache }), }, //highlight-end }; @@ -128,48 +127,69 @@ const resolvers = { > 📣 **New in Apollo Server 4**: Apollo Server no longer automatically provides it's cache to data sources. [See here for more details](#datasources). -You can pass your `RESTDataSource` subclasses a cache (such as Apollo Server's default cache) to store the results of past fetches: +As shown in the above code snippet, by default, each `RESTDataSource` subclass accepts a cache argument (such as Apollo Server's default cache) to store the results of past fetches: + +```ts disableCopy +class MoviesAPI extends RESTDataSource { + override baseURL = 'https://movies-api.example.com/'; + + // We can omit the constructor function here because + // RESTDataSource accepts a cache argument by default +} + +// server set up, etc. + +const { url } = await startStandaloneServer(server, { + context: async ({ req }) => { + const { cache } = server; // highlight-line + return { + dataSources: { + // highlight-start + moviesAPI: new MoviesAPI({ cache }), + personalizationAPI: new PersonalizationAPI({ cache }), + // highlight-end + }, + }; + }, +}); +``` + +If your data source accepts other arguments, ensure you do define a constructor function to accept those arguments, like so: + ```ts -import { RESTDataSource } from '@apollo/datasource-rest'; -// KeyValueCache is the type of Apollo server's default cache -import { KeyValueCache } from '@apollo/utils.keyvaluecache'; import { ApolloServer } from '@apollo/server'; import { startStandaloneServer } from '@apollo/server/standalone'; +import { RESTDataSource } from '@apollo/datasource-rest'; +// KeyValueCache is the type of Apollo server's default cache +import type { KeyValueCache } from '@apollo/utils.keyvaluecache'; -class MoviesAPI extends RESTDataSource { - baseURL = 'https://movies-api.example.com/'; +class PersonalizationAPI extends RESTDataSource { + override baseURL = 'https://movies-api.example.com/'; + private token: string; //highlight-start - constructor(options: {cache: KeyValueCache }) { - super(options); // this should send our server's `cache` through + constructor(options: { token: string; cache: KeyValueCache }) { + super(options); // this sends our server's `cache` through + this.token = options.token; } //highlight-end // data fetching methods, etc. } -interface ContextValue { - dataSources: { - moviesAPI: MoviesAPI; - }; -} - -const server = new ApolloServer({ - typeDefs, - resolvers, -}); +// set up server and context typing const { url } = await startStandaloneServer(server, { context: async ({ req }) => { + const token = getTokenFromRequest(req); const { cache } = server; return { - token, //highlight-start dataSources: { - moviesAPI: new MoviesAPI({ cache, token }), + personalizationApi: new PersonalizationAPI({ cache, token }), }, //highlight-end }; @@ -179,27 +199,13 @@ const { url } = await startStandaloneServer(server, { -If you want Apollo Server to use another cache backend, you can provide its constructor a _different_ cache object that implements the [`KeyValueCache` interface](https://github.com/apollographql/apollo-utils/tree/main/packages/keyValueCache#keyvaluecache-interface): - -```ts title="server.ts" -const server = new ApolloServer({ - typeDefs, - resolvers, - cache: new MyCustomKeyValueCache() -}); -``` - -### Using an external cache backend - When running multiple instances of your server, you should use a shared cache backend. This enables one server instance to use the cached result from _another_ instance. -Apollo Server supports using [Memcached](https://memcached.org/), [Redis](https://redis.io/), or other cache backends via the [`keyv`](https://www.npmjs.com/package/keyv) package. For examples, see [Configuring external caching](../performance/cache-backends#configuring-external-caching). - -You can also choose to implement your own cache backend. For more information, see [Implementing your own cache backend](../performance/cache-backends#implementing-your-own-cache-backend). +> If you want to use another cache backend with Apollo Sever, see [Configuring external caching](../performance/cache-backends#configuring-external-caching). ## HTTP Methods -`RESTDataSource` includes convenience methods for common REST API request methods: `get`, `post`, `put`, `patch`, and `delete` ([see the source](https://github.com/apollographql/datasource-rest/blob/main/src/RESTDataSource.ts#L163)). +`RESTDataSource` includes convenience methods for common REST API request methods: `get`, `post`, `put`, `patch`, and `delete` ([see the source](https://github.com/apollographql/datasource-rest/blob/25862e18d8b35e324c150654c5686ed317b3fca8/src/RESTDataSource.ts#L163)). An example of each is shown below: @@ -207,7 +213,7 @@ An example of each is shown below: ```ts class MoviesAPI extends RESTDataSource { - baseURL = 'https://movies-api.example.com/'; + override baseURL = 'https://movies-api.example.com/'; // GET async getMovie(id) { @@ -267,7 +273,7 @@ For all HTTP convenience methods, the **first parameter** is the relative path o The **second parameter** depends on the HTTP method: -- For HTTP methods with a request body (`post`, `put`, `patch`), the second parameter _is_ the request body (e.g., `{ body: { id: movie.id } }`). +- For HTTP methods with a request body (`post`, `put`, `patch`), the second parameter is an _object_ that contains the request body (e.g., `{ body: { id: movie.id } }`), `method`, and `cacheOptions`. - For HTTP methods _without_ a request body, the second parameter is an object with keys and values corresponding to the request's query parameters. For all methods, the **third parameter** is an `init` object that enables you to provide additional options (such as headers and referrers) to the `fetch` API that's used to send the request. For details, [see MDN's fetch docs](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters). @@ -280,39 +286,55 @@ Data sources also have access to the GraphQL operation context, which is useful ### Setting a header +> If you're using TypeScript, make sure to import the `WillSendRequestOptions` type. + ```ts import { RESTDataSource, WillSendRequestOptions } from '@apollo/datasource-rest'; +import type { KeyValueCache } from '@apollo/utils.keyvaluecache'; class PersonalizationAPI extends RESTDataSource { - override baseURL = 'https://personalization-api.example.com/'; + override baseURL = 'https://movies-api.example.com/'; + private token: string; - constructor(private token: string) { - super(); + constructor(options: { token: string; cache: KeyValueCache }) { + super(options); + this.token = options.token; } + // highlight-start override willSendRequest(request: WillSendRequestOptions) { request.headers['authorization'] = this.token; } + // highlight-end } ``` -> If you're using TypeScript, make sure to import the `WillSendRequestOptions` type. - ### Adding a query parameter ```ts -import { RESTDataSource } from '@apollo/datasource-rest'; +import { RESTDataSource, WillSendRequestOptions } from '@apollo/datasource-rest'; +import type { KeyValueCache } from '@apollo/utils.keyvaluecache'; class PersonalizationAPI extends RESTDataSource { - willSendRequest(request) { + override baseURL = 'https://movies-api.example.com/'; + private token: string; + + constructor(options: { token: string; cache: KeyValueCache }) { + super(options); + this.token = options.token; + } + + // highlight-start + override willSendRequest(request: WillSendRequestOptions) { request.params.set('api_key', this.token); } + // highlight-end } ``` @@ -326,10 +348,14 @@ In some cases, you'll want to set the URL based on the environment or other cont ```ts import { RESTDataSource, RequestOptions } from '@apollo/datasource-rest'; +import type { KeyValueCache } from '@apollo/utils.keyvaluecache'; class PersonalizationAPI extends RESTDataSource { - constructor(private token: string) { - super(); + private token: string; + + constructor(options: { token: string; cache: KeyValueCache }) { + super(options); + this.token = options.token; } override async resolveURL(path: string, request: RequestOptions) { @@ -365,30 +391,31 @@ Most REST APIs don't support batching. When they do, using a batched endpoint ca We recommend that you restrict batching to requests that _can't_ be cached. In these cases, you can take advantage of DataLoader as a private implementation detail inside your `RESTDataSource`: ```ts +import DataLoader from 'dataloader'; +import { RESTDataSource, WillSendRequestOptions } from '@apollo/datasource-rest'; +import type { KeyValueCache } from '@apollo/utils.keyvaluecache'; + class PersonalizationAPI extends RESTDataSource { - constructor() { - super(); - this.baseURL = 'https://personalization-api.example.com/'; + override baseURL = 'https://movies-api.example.com/'; + private token: string; + + constructor(options: { token: string; cache: KeyValueCache }) { + super(options); // this should send our server's `cache` through + this.token = options.token; } - willSendRequest(request) { - request.headers.set('Authorization', this.context.token); + override willSendRequest(request: WillSendRequestOptions) { + request.headers['authorization'] = this.token; } private progressLoader = new DataLoader(async (ids) => { - const progressList = await this.get('progress', { - ids: ids.join(','), - }); - return ids.map(id => - progressList.find((progress) => progress.id === id), - ); + const progressList = await this.get('progress', { params: { ids: ids.join(',') } }); + return ids.map((id) => progressList.find((progress) => progress.id === id)); }); async getProgressFor(id) { return this.progressLoader.load(id); } } -``` - - +``` \ No newline at end of file diff --git a/docs/source/migration.mdx b/docs/source/migration.mdx index a1a77db8ea1..b585882428f 100644 --- a/docs/source/migration.mdx +++ b/docs/source/migration.mdx @@ -520,6 +520,7 @@ class ContextValue { public dataSources: { moviesAPI: MoviesAPI; }; + constructor({ req, server }: { req: IncomingMessage; server: ApolloServer }) { this.token = getTokenFromRequest(req); const { cache } = server; From 6f6511e53be0fb87ec46652eef34eb3647fade9c Mon Sep 17 00:00:00 2001 From: Rose Koron Date: Thu, 25 Aug 2022 10:45:51 -0700 Subject: [PATCH 07/21] Add last of Trevor's feedback --- docs/source/config.json | 1 - docs/source/data/fetching-data.mdx | 10 ++++++---- docs/source/data/fetching-rest.mdx | 2 ++ docs/source/data/resolvers.mdx | 6 ++++-- docs/source/migration.mdx | 32 +++++++++++++++--------------- 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/docs/source/config.json b/docs/source/config.json index 2c90486fc4b..c93e64f8ee4 100644 --- a/docs/source/config.json +++ b/docs/source/config.json @@ -17,7 +17,6 @@ }, "Fetching Data": { "Resolvers": "/data/resolvers", - "Fetching data": "/data/fetching-data", "Fetching from REST": "/data/fetching-rest", "Error handling": "/data/errors", "Subscriptions": "/data/subscriptions" diff --git a/docs/source/data/fetching-data.mdx b/docs/source/data/fetching-data.mdx index 20417d2b650..a28b4219289 100644 --- a/docs/source/data/fetching-data.mdx +++ b/docs/source/data/fetching-data.mdx @@ -1,9 +1,9 @@ --- title: Fetching Data -description: Using RESTDataSource to fetch data from REST APIs +description: Fetching data from data sources besides REST --- - +```ts + +``` diff --git a/docs/source/data/fetching-rest.mdx b/docs/source/data/fetching-rest.mdx index 443400479c7..df3477586b2 100644 --- a/docs/source/data/fetching-rest.mdx +++ b/docs/source/data/fetching-rest.mdx @@ -16,7 +16,9 @@ flowchart LR; client --Sends query--> ApolloServer; ``` +<--! TODO(AS4) add this note back in when fetching data article is ready > For more information about fetching from data sources other than a REST API, see [Fetching Data](./fetching-data). +--> See the [`@apollo/datasource-rest` page](https://www.npmjs.com/package/@apollo/datasource-rest) for the full details of the `RESTDataSource` API. diff --git a/docs/source/data/resolvers.mdx b/docs/source/data/resolvers.mdx index 4e0d7d25c95..047ed3e3543 100644 --- a/docs/source/data/resolvers.mdx +++ b/docs/source/data/resolvers.mdx @@ -77,7 +77,8 @@ const users = [ ]; ``` -> To learn how to fetch data from an external source (like a database or REST API), see [Data sources](./data-sources/). + +> To learn how to fetch data from a REST API, see [Fetching from REST](./fetching-rest/). Now we can define a resolver for the `user` field, like so: @@ -381,7 +382,8 @@ Resolver functions are passed four arguments: `parent`, `args`, `context`, and ` > ⚠️ The Apollo Server 4 alpha doesn't currently support using [`RESTDataSource`](../migration#datasources), a class commonly used to fetch data from a database or a REST API. This feature is in active development, so check back frequently for updates. -The `context` argument is useful for passing things that any resolver might need, like [authentication scope](https://blog.apollographql.com/authorization-in-graphql-452b1c402a9), database connections, and custom fetch functions. If you're using [dataloaders to batch requests](./data-sources/#using-with-dataloader) across resolvers, you can attach them to the `context` as well. + +The `context` argument is useful for passing things that any resolver might need, like [authentication scope](https://blog.apollographql.com/authorization-in-graphql-452b1c402a9), database connections, and custom fetch functions. If you're using [dataloaders to batch requests](./fetch-rest/#using-with-dataloader) across resolvers, you can attach them to the `context` as well. **Resolvers should never destructively modify the `context` argument.** This ensures consistency across all resolvers and prevents unexpected errors. diff --git a/docs/source/migration.mdx b/docs/source/migration.mdx index b585882428f..11c96f13c2b 100644 --- a/docs/source/migration.mdx +++ b/docs/source/migration.mdx @@ -376,18 +376,18 @@ For example, below, we use the `RESTDataSource` class to create a `DataSource` w ```ts title="Apollo Server 3" -import { RESTDataSource } from 'apollo-datasource-rest'; +import { RESTDataSource, RequestOptions } from 'apollo-datasource-rest'; import { ApolloServer } from 'apollo-server'; class MoviesAPI extends RESTDataSource { //highlight-line - baseURL = 'https://movies-api.example.com/'; + override baseURL = 'https://movies-api.example.com/'; - willSendRequest(request: RequestOptions) { + override willSendRequest(request: RequestOptions) { request.headers.set('Authorization', this.context.token); } - async getMovie(id: string): Movie { - return this.get(`movies/${encodeURIComponent(id)}`); + async getMovie(id: string): Promise { + return this.get(`movies/${encodeURIComponent(id)}`); } } @@ -425,27 +425,27 @@ Below is how you write the same code in Apollo Server 4. ```ts title="Apollo Server 4" -import { RESTDataSource, RequestOptions } from '@apollo/datasource-rest'; +import { RESTDataSource, WillSendRequestOptions } from '@apollo/datasource-rest'; // KeyValueCache is the type of Apollo server's default cache -import { KeyValueCache } from '@apollo/utils.keyvaluecache'; +import type { KeyValueCache } from '@apollo/utils.keyvaluecache'; import { ApolloServer } from '@apollo/server'; import { startStandaloneServer } from '@apollo/server/standalone'; class MoviesAPI extends RESTDataSource { // highlight-line - baseURL = 'https://movies-api.example.com/'; + override baseURL = 'https://movies-api.example.com/'; private token: string; constructor(options: { token: string; cache: KeyValueCache }) { - super(options); // this should send our server's `cache` through + super(options); // this sends our server's `cache` through this.token = options.token; } - willSendRequest(request: RequestOptions) { + override willSendRequest(request: WillSendRequestOptions) { request.headers['authorization'] = this.token; } async getMovie(id: string): Promise { - return this.get(`movies/${encodeURIComponent(id)}`); + return this.get(`movies/${encodeURIComponent(id)}`); } } @@ -490,14 +490,14 @@ If you want to access your entire context's value within your `DataSource`, you ```ts -import { RESTDataSource, RequestOptions } from '@apollo/datasource-rest'; //highlight-line +import { RESTDataSource, WillSendRequestOptions } from '@apollo/datasource-rest'; //highlight-line import { KeyValueCache } from '@apollo/utils.keyvaluecache'; import { ApolloServer } from '@apollo/server'; import { startStandaloneServer } from '@apollo/server/standalone'; import { IncomingMessage } from 'http'; class MoviesAPI extends RESTDataSource { - baseURL = 'https://movies-api.example.com/'; + override baseURL = 'https://movies-api.example.com/'; private contextValue: ContextValue; constructor(options: { contextValue: ContextValue; cache: KeyValueCache }) { @@ -505,12 +505,12 @@ class MoviesAPI extends RESTDataSource { this.contextValue = options.contextValue; } - willSendRequest(request: RequestOptions) { + override willSendRequest(request: WillSendRequestOptions) { request.headers['authorization'] = this.contextValue.token; } - async getMovie(id: string): Promise { - return this.get(`movies/${encodeURIComponent(id)}`); + async getMovie(id):Promise { + return this.get(`movies/${encodeURIComponent(id)}`); } } From c7c6c4dcdb5f82c98224b31d142dabdbf6dcaaed Mon Sep 17 00:00:00 2001 From: Rose Koron Date: Thu, 25 Aug 2022 10:47:17 -0700 Subject: [PATCH 08/21] Fix links --- docs/source/performance/cache-backends.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/performance/cache-backends.mdx b/docs/source/performance/cache-backends.mdx index e83534a7477..035aad184d1 100644 --- a/docs/source/performance/cache-backends.mdx +++ b/docs/source/performance/cache-backends.mdx @@ -3,7 +3,7 @@ title: Configuring cache backends description: How to configure Apollo Server's cache --- -Many Apollo Server features take advantage of a cache backend (these features include [automatic persisted queries](./apq), the [response cache plugin](./caching#caching-with-responsecacheplugin-advanced), and [`RESTDataSource`](../data/data-sources#restdatasource-reference)). Apollo Server uses an in-memory cache by default, but you can configure it to use a different backend, such as Redis or Memcached. +Many Apollo Server features take advantage of a cache backend (these features include [automatic persisted queries](./apq), the [response cache plugin](./caching#caching-with-responsecacheplugin-advanced), and [`RESTDataSource`](../data/fetch-rest)). Apollo Server uses an in-memory cache by default, but you can configure it to use a different backend, such as Redis or Memcached. You can specify a cache backend by passing a `cache` option to the `ApolloServer` constructor. Your specified cache backend must implement the [`KeyValueCache`](https://github.com/apollographql/apollo-utils/tree/main/packages/keyValueCache#keyvaluecache-interface) interface from the `@apollo/utils.keyvaluecache` package. From e221d26aa199bda829f4dd7a0ff66fbff85245a9 Mon Sep 17 00:00:00 2001 From: Rose Koron Date: Thu, 25 Aug 2022 10:49:07 -0700 Subject: [PATCH 09/21] Pass spell check --- docs/source/data/fetching-data.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/data/fetching-data.mdx b/docs/source/data/fetching-data.mdx index a28b4219289..4a707271a0e 100644 --- a/docs/source/data/fetching-data.mdx +++ b/docs/source/data/fetching-data.mdx @@ -6,7 +6,7 @@ description: Fetching data from data sources besides REST - All data source implementations extend the generic [`DataSource` abstract class](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-datasource/src/index.ts), which is included in the `apollo-datasource` package. Subclasses of a `DataSource` should define whatever logic is required to communicate with a particular store or API. @@ -73,7 +72,6 @@ Apollo and the larger community maintain the following open-source implementatio To make any of the Above Apollo Server 3 data sources work you have to first construct the data source before calling `initialize` and passing in Apollo Server's cache, like so. - ## Adding data sources to Apollo Server's context From a9fb3cd865f80bb8999629e441903ed0b3b272a0 Mon Sep 17 00:00:00 2001 From: Rose Koron Date: Thu, 25 Aug 2022 11:32:04 -0700 Subject: [PATCH 13/21] Comment out most of article --- docs/source/data/fetching-data.mdx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/source/data/fetching-data.mdx b/docs/source/data/fetching-data.mdx index c43e4afa603..c5571ba7190 100644 --- a/docs/source/data/fetching-data.mdx +++ b/docs/source/data/fetching-data.mdx @@ -49,11 +49,12 @@ Do we want to tell people how to create data sources themselves? Is there an exa ## Open-source implementations > **New in Apollo Server 4**: if you are using an open-source data source implementation from Apollo Server 3 expand the below example to see how to make it work in Apollo Server 4. + + All data source implementations extend the generic [`DataSource` abstract class](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-datasource/src/index.ts), which is included in the `apollo-datasource` package. Subclasses of a `DataSource` should define whatever logic is required to communicate with a particular store or API. @@ -113,7 +114,9 @@ console.log(`🚀 Server ready at ${url}`); - +--> + \ No newline at end of file From d364256881c416c0bad7228955dc1e23f925be48 Mon Sep 17 00:00:00 2001 From: Rose Koron Date: Thu, 25 Aug 2022 12:57:42 -0700 Subject: [PATCH 14/21] Remove article for now --- docs/source/data/fetching-data.mdx | 161 +---------------------------- 1 file changed, 1 insertion(+), 160 deletions(-) diff --git a/docs/source/data/fetching-data.mdx b/docs/source/data/fetching-data.mdx index c5571ba7190..a5ada9597e8 100644 --- a/docs/source/data/fetching-data.mdx +++ b/docs/source/data/fetching-data.mdx @@ -3,163 +3,4 @@ title: Fetching Data description: Fetching data from data sources besides REST --- - - -Apollo Server can use to encapsulate fetching data from a particular source, such as a database or a REST API. These classes help handle caching, deduplication, and errors while resolving operations. - -Your server can use any number of different data sources. You don't _have_ to use data sources to fetch data, but they're strongly recommended. - -```mermaid -flowchart LR; - restAPI(REST API); - db(Database); - subgraph ApolloServer; - restDataSource(RESTDataSource); - DBDataSource(DBDataSource); - end - restDataSource --Fetches data--> restAPI; - DBDataSource --Fetches data--> db; - client(ApolloClient); - client --Sends query--> ApolloServer; - class restAPI,db secondary; -``` - - - - -## Open-source implementations - -> **New in Apollo Server 4**: if you are using an open-source data source implementation from Apollo Server 3 expand the below example to see how to make it work in Apollo Server 4. - - - \ No newline at end of file + From 1bbc9328bc2e201d226d76aec818b0a6ab0d174e Mon Sep 17 00:00:00 2001 From: Rose Koron Date: Thu, 25 Aug 2022 14:16:47 -0700 Subject: [PATCH 15/21] Final touches --- docs/source/data/fetching-rest.mdx | 54 +++++++++++----------- docs/source/data/resolvers.mdx | 18 ++++---- docs/source/performance/cache-backends.mdx | 6 +-- docs/source/security/authentication.mdx | 16 +++---- 4 files changed, 45 insertions(+), 49 deletions(-) diff --git a/docs/source/data/fetching-rest.mdx b/docs/source/data/fetching-rest.mdx index df3477586b2..60d1e083448 100644 --- a/docs/source/data/fetching-rest.mdx +++ b/docs/source/data/fetching-rest.mdx @@ -16,11 +16,11 @@ flowchart LR; client --Sends query--> ApolloServer; ``` -<--! TODO(AS4) add this note back in when fetching data article is ready -> For more information about fetching from data sources other than a REST API, see [Fetching Data](./fetching-data). + -See the [`@apollo/datasource-rest` page](https://www.npmjs.com/package/@apollo/datasource-rest) for the full details of the `RESTDataSource` API. +> See the [`@apollo/datasource-rest` page](https://www.npmjs.com/package/@apollo/datasource-rest) for the full details of the `RESTDataSource` API. ## Creating subclasses @@ -61,7 +61,7 @@ You can extend the `RESTDataSource` class to implement whatever data-fetching me ## Adding data sources to Apollo Server's context -You can then provide your `RESTDataSource` subclasses to the `context` initialization function, like so: +You can add data sources to the `context` initialization function, like so: @@ -84,9 +84,9 @@ const { url } = await startStandaloneServer(server, { context: async () => { const { cache } = server; // highlight-line return { - //highlight-start // We create new instances of our data sources with each request, // passing in our server's cache. + //highlight-start dataSources: { moviesAPI: new MoviesAPI({ cache }), personalizationAPI: new PersonalizationAPI({ cache }), @@ -102,12 +102,11 @@ console.log(`🚀 Server ready at ${url}`); -Apollo Server calls the [the `context` initialization](../resolvers/#the-context-argument) function for _every incoming operation_. This means: -- As shown above, with every operation `context` returns an _object_ containing new instances of your `RESTDataSource` subclasses (in this case, `MoviesAPI` and `PersonalizationAPI`). -- These subclasses are accessible from the [the `context` argument](./resolvers/#the-context-argument) that's passed between your server's resolvers. +Apollo Server calls [the `context` initialization](../resolvers/#the-context-argument) function for _every incoming operation_. This means: +- For every operation, `context` returns an _object_ containing new instances of your `RESTDataSource` subclasses (in this case, `MoviesAPI` and `PersonalizationAPI`). - The **`context` function should create a new instance of each `RESTDataSource` subclass for each operation.** If multiple operations share a single data source instance, you might accidentally combine results from multiple operations. -Your resolvers can now access your data sources from the shared `context` object and use them to fetch data: +Your resolvers can then access your data sources from the shared `context` object and use them to fetch data: ```ts title="resolvers.ts" const resolvers = { @@ -127,15 +126,15 @@ const resolvers = { ## Caching -> 📣 **New in Apollo Server 4**: Apollo Server no longer automatically provides it's cache to data sources. [See here for more details](#datasources). +> 📣 **New in Apollo Server 4**: Apollo Server no longer automatically provides its cache to data sources. [See here for more details](#datasources). -As shown in the above code snippet, by default, each `RESTDataSource` subclass accepts a cache argument (such as Apollo Server's default cache) to store the results of past fetches: +As shown in the above code snippet, by default, each `RESTDataSource` subclass accepts a `cache` argument (e.g., Apollo Server's default cache) to store the results of past fetches: ```ts disableCopy class MoviesAPI extends RESTDataSource { override baseURL = 'https://movies-api.example.com/'; - // We can omit the constructor function here because + // We omit the constructor function here because // RESTDataSource accepts a cache argument by default } @@ -147,8 +146,8 @@ const { url } = await startStandaloneServer(server, { return { dataSources: { // highlight-start - moviesAPI: new MoviesAPI({ cache }), - personalizationAPI: new PersonalizationAPI({ cache }), + moviesAPI: new MoviesAPI({ cache }), + personalizationAPI: new PersonalizationAPI({ cache }), // highlight-end }, }; @@ -156,8 +155,7 @@ const { url } = await startStandaloneServer(server, { }); ``` -If your data source accepts other arguments, ensure you do define a constructor function to accept those arguments, like so: - +If your `RESTDataSource` subclass accepts multiple arguments, make sure you add a constructor function, like so: @@ -173,7 +171,7 @@ class PersonalizationAPI extends RESTDataSource { private token: string; //highlight-start - constructor(options: { token: string; cache: KeyValueCache }) { + constructor(options: { token: string; cache: KeyValueCache }) { super(options); // this sends our server's `cache` through this.token = options.token; } @@ -182,7 +180,7 @@ class PersonalizationAPI extends RESTDataSource { // data fetching methods, etc. } -// set up server and context typing +// set up server, context typing, etc. const { url } = await startStandaloneServer(server, { context: async ({ req }) => { @@ -203,7 +201,7 @@ const { url } = await startStandaloneServer(server, { When running multiple instances of your server, you should use a shared cache backend. This enables one server instance to use the cached result from _another_ instance. -> If you want to use another cache backend with Apollo Sever, see [Configuring external caching](../performance/cache-backends#configuring-external-caching). +> If you want to configure or replace Apollo Sever's default cache, see [Configuring external caching](../performance/cache-backends) for more details. ## HTTP Methods @@ -260,7 +258,7 @@ class MoviesAPI extends RESTDataSource { -> Note the use of [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent). This is a standard function that encodes special characters in a URI, preventing a possible injection attack vector. +Note the use of [`encodeURIComponent`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) in the above snippet. This is a standard function that encodes special characters in a URI, preventing a possible injection attack vector. For a simple example, suppose our REST endpoint responded to the following URLs: @@ -275,7 +273,7 @@ For all HTTP convenience methods, the **first parameter** is the relative path o The **second parameter** depends on the HTTP method: -- For HTTP methods with a request body (`post`, `put`, `patch`), the second parameter is an _object_ that contains the request body (e.g., `{ body: { id: movie.id } }`), `method`, and `cacheOptions`. +- For HTTP methods with a request body (`post`, `put`, `patch`), the second parameter is an _object_ that contains the `method` `cacheOptions`, and request body (e.g., `{ body: { id: movie.id } }`). - For HTTP methods _without_ a request body, the second parameter is an object with keys and values corresponding to the request's query parameters. For all methods, the **third parameter** is an `init` object that enables you to provide additional options (such as headers and referrers) to the `fetch` API that's used to send the request. For details, [see MDN's fetch docs](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters). @@ -286,10 +284,10 @@ For all methods, the **third parameter** is an `init` object that enables you to Data sources also have access to the GraphQL operation context, which is useful for storing a user token or other relevant information. -### Setting a header - > If you're using TypeScript, make sure to import the `WillSendRequestOptions` type. +### Setting a header + ```ts @@ -300,7 +298,7 @@ class PersonalizationAPI extends RESTDataSource { override baseURL = 'https://movies-api.example.com/'; private token: string; - constructor(options: { token: string; cache: KeyValueCache }) { + constructor(options: { token: string; cache: KeyValueCache }) { super(options); this.token = options.token; } @@ -327,11 +325,11 @@ class PersonalizationAPI extends RESTDataSource { override baseURL = 'https://movies-api.example.com/'; private token: string; - constructor(options: { token: string; cache: KeyValueCache }) { + constructor(options: { token: string; cache: KeyValueCache }) { super(options); this.token = options.token; } - + // highlight-start override willSendRequest(request: WillSendRequestOptions) { request.params.set('api_key', this.token); @@ -355,7 +353,7 @@ import type { KeyValueCache } from '@apollo/utils.keyvaluecache'; class PersonalizationAPI extends RESTDataSource { private token: string; - constructor(options: { token: string; cache: KeyValueCache }) { + constructor(options: { token: string; cache: KeyValueCache }) { super(options); this.token = options.token; } @@ -420,4 +418,4 @@ class PersonalizationAPI extends RESTDataSource { } } -``` \ No newline at end of file +``` diff --git a/docs/source/data/resolvers.mdx b/docs/source/data/resolvers.mdx index 047ed3e3543..71c73839534 100644 --- a/docs/source/data/resolvers.mdx +++ b/docs/source/data/resolvers.mdx @@ -26,7 +26,7 @@ We want to define resolvers for the `numberSix` and `numberSeven` fields of the Those resolver definitions look like this: -```ts +```ts const resolvers = { Query: { numberSix() { @@ -64,7 +64,7 @@ We want to be able to query the `user` field to fetch a user by its `id`. To achieve this, our server needs access to user data. For this contrived example, assume our server defines the following hardcoded array: -```ts +```ts const users = [ { id: '1', @@ -77,12 +77,9 @@ const users = [ ]; ``` - -> To learn how to fetch data from a REST API, see [Fetching from REST](./fetching-rest/). - Now we can define a resolver for the `user` field, like so: -```ts +```ts const resolvers = { Query: { user(parent, args, context, info) { @@ -162,6 +159,9 @@ console.log(`🚀 Server listening at: ${url}`); Note that you can define your resolvers across as many different files and objects as you want, as long as you merge all of them into a single resolver map that's passed to the `ApolloServer` constructor. + +> To learn how to fetch data from a REST API, see [Fetching from REST](./fetching-rest/). + ## Resolver chains Whenever a query asks for a field that returns an object type, the query _also_ asks for _at least one field_ of that object (if it didn't, there would be no reason to include the object in the query). A query always "bottoms out" on fields that return a [scalar](../schema/schema/#scalar-types), an [enum](../schema/schema/#enum-types), or a list of these. @@ -380,10 +380,8 @@ Resolver functions are passed four arguments: `parent`, `args`, `context`, and ` ### The `context` argument -> ⚠️ The Apollo Server 4 alpha doesn't currently support using [`RESTDataSource`](../migration#datasources), a class commonly used to fetch data from a database or a REST API. This feature is in active development, so check back frequently for updates. - -The `context` argument is useful for passing things that any resolver might need, like [authentication scope](https://blog.apollographql.com/authorization-in-graphql-452b1c402a9), database connections, and custom fetch functions. If you're using [dataloaders to batch requests](./fetch-rest/#using-with-dataloader) across resolvers, you can attach them to the `context` as well. +The `context` argument is useful for passing things that any resolver might need, like [authentication scope](https://blog.apollographql.com/authorization-in-graphql-452b1c402a9), [sources for fetching data](../fetching-rest/), database connections, and custom fetch functions. If you're using [dataloaders to batch requests](../fetching-rest/#using-with-dataloader) across resolvers, you can attach them to the `context` as well. **Resolvers should never destructively modify the `context` argument.** This ensures consistency across all resolvers and prevents unexpected errors. @@ -441,7 +439,7 @@ The `context` function should be *asynchronous* and return an object, which is t Because the `context` initialization function is asynchronous, you can use it to establish database connections and wait for other operations to complete: -```ts +```ts context: async () => ({ db: await client.connect(), }) diff --git a/docs/source/performance/cache-backends.mdx b/docs/source/performance/cache-backends.mdx index 035aad184d1..e2c1de8d3f7 100644 --- a/docs/source/performance/cache-backends.mdx +++ b/docs/source/performance/cache-backends.mdx @@ -3,7 +3,7 @@ title: Configuring cache backends description: How to configure Apollo Server's cache --- -Many Apollo Server features take advantage of a cache backend (these features include [automatic persisted queries](./apq), the [response cache plugin](./caching#caching-with-responsecacheplugin-advanced), and [`RESTDataSource`](../data/fetch-rest)). Apollo Server uses an in-memory cache by default, but you can configure it to use a different backend, such as Redis or Memcached. +Many Apollo Server features take advantage of a cache backend (these features include [automatic persisted queries](./apq), the [response cache plugin](./caching#caching-with-responsecacheplugin-advanced), and [`RESTDataSource`](../data/fetching-rest)). Apollo Server uses an in-memory cache by default, but you can configure it to use a different backend, such as Redis or Memcached. You can specify a cache backend by passing a `cache` option to the `ApolloServer` constructor. Your specified cache backend must implement the [`KeyValueCache`](https://github.com/apollographql/apollo-utils/tree/main/packages/keyValueCache#keyvaluecache-interface) interface from the `@apollo/utils.keyvaluecache` package. @@ -47,7 +47,7 @@ const server = new ApolloServer({ ## Configuring external caching -Apollo no longer maintains any caching backends directly. Instead, we recommend using the [`keyv`](https://www.npmjs.com/package/keyv) package along with the [`KeyvAdapter`](https://github.com/apollographql/apollo-utils/tree/main/packages/keyvAdapter#keyvadapter-class) class provided by the `@apollo/utils.keyvadapter` package. +Apollo no longer maintains any caching backends directly. Instead, we recommend using the [`keyv`](https://www.npmjs.com/package/keyv) package along with the [`KeyvAdapter`](https://github.com/apollographql/apollo-utils/tree/main/packages/keyvAdapter#keyvadapter-class) class provided by the `@apollo/utils.keyvadapter` package. `KeyvAdapter` wraps a `Keyv` instance and implements the `KeyValueCache` interface which is required by Apollo Server. You can use the `KeyvAdapter` class to wrap a `Keyv` instance and provide it to the `cache` option of the `ApolloServer` constructor like so: @@ -207,4 +207,4 @@ Versions of Apollo Server prior to 3.9 use the `apollo-server-caching` package t The `InMemoryLRUCache` class has also moved to the `@apollo/utils.keyvaluecache` package. The `InMemoryLRUCache` class now uses version 7 of `lru-cache`, accepting different configuration options and no longer allowing a cache to be unbounded. -The `apollo-server-cache-redis` and `apollo-server-cache-memcached` packages are no longer receiving updates; we recommend using `keyv` instead, as shown above. \ No newline at end of file +The `apollo-server-cache-redis` and `apollo-server-cache-memcached` packages are no longer receiving updates; we recommend using `keyv` instead, as shown above. diff --git a/docs/source/security/authentication.mdx b/docs/source/security/authentication.mdx index 8ab1ff4d083..e4820b47342 100644 --- a/docs/source/security/authentication.mdx +++ b/docs/source/security/authentication.mdx @@ -149,7 +149,7 @@ One choice to make when building out our resolvers is what an unauthorized field Now let's expand that example a little further, and only allow users with an `admin` role to look at our user list. After all, we probably don't want just anyone to have access to all our users. -```ts +```ts users: (parent, args, context) => { if (!context.user || !context.user.roles.includes('admin')) return null; return context.models.User.getAll(); @@ -166,11 +166,11 @@ As our server gets more complex, there will probably be multiple places in the s -As always, we recommend moving the actual data fetching and transformation logic from your resolvers to data sources or model objects that each represent a concept from your application: `User`, `Post`, etc. This allows you to make your resolvers a thin routing layer, and put all of your business logic in one place. +As always, we recommend moving the actual data fetching and transformation logic from your resolvers to [data sources](../fetching-rest/) or model objects that each represent a concept from your application: `User`, `Post`, etc. This allows you to make your resolvers a thin routing layer, and put all of your business logic in one place. For example, a model file for `User` would include all the logic for operating on users, and might look something like this: -```ts +```ts export const User = { getAll: () => { /* fetching/transformation logic for all users */ @@ -186,7 +186,7 @@ export const User = { In the following example, our schema has multiple ways to request a single user: -```graphql +```graphql type Query { user(id: ID!): User article(id: ID!): Article @@ -208,7 +208,7 @@ Rather than having the same fetching logic for a single user in two separate pla You may have noticed that our models also exist on the context, alongside the user object we added earlier. We can add the models to the context in exactly the same way as we did the user. -```ts +```ts context: async ({ req }) => { // get the user token from the headers const token = req.headers.authentication || ''; @@ -237,7 +237,7 @@ context: async ({ req }) => { Starting to generate our models with a function requires a small refactor, that would leave our User model looking something like this: -```ts +```ts export const generateUserModel = ({ user }) => ({ getAll: () => { /* fetching/transform logic for all users */ @@ -253,7 +253,7 @@ export const generateUserModel = ({ user }) => ({ Now any model method in `User` has access to the same `user` information that resolvers already had, allowing us to refactor the `getAll` function to do the permissions check directly rather than having to put it in the resolver: -```ts +```ts getAll: () => { if (!user || !user.roles.includes('admin')) return null; return fetch('http://myurl.com/users'); @@ -315,4 +315,4 @@ export const generateUserModel = ({ req }) => ({ }); ``` -If your REST endpoint is already backed by some form of authorization, this cuts down a lot of the logic that needs to get built in the GraphQL layer. This can be a great option when building a GraphQL API over an existing REST API that has everything you need already built in. \ No newline at end of file +If your REST endpoint is already backed by some form of authorization, this cuts down a lot of the logic that needs to get built in the GraphQL layer. This can be a great option when building a GraphQL API over an existing REST API that has everything you need already built in. From 2e54cfe6d13c39e019284fde17c41ebd47432174 Mon Sep 17 00:00:00 2001 From: Rose Koron Date: Thu, 25 Aug 2022 15:39:09 -0700 Subject: [PATCH 16/21] Fix links --- docs/source/data/fetching-rest.mdx | 2 +- docs/source/data/resolvers.mdx | 2 +- docs/source/security/authentication.mdx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/data/fetching-rest.mdx b/docs/source/data/fetching-rest.mdx index 60d1e083448..c00abb706b4 100644 --- a/docs/source/data/fetching-rest.mdx +++ b/docs/source/data/fetching-rest.mdx @@ -102,7 +102,7 @@ console.log(`🚀 Server ready at ${url}`); -Apollo Server calls [the `context` initialization](../resolvers/#the-context-argument) function for _every incoming operation_. This means: +Apollo Server calls [the `context` initialization](./resolvers/#the-context-argument) function for _every incoming operation_. This means: - For every operation, `context` returns an _object_ containing new instances of your `RESTDataSource` subclasses (in this case, `MoviesAPI` and `PersonalizationAPI`). - The **`context` function should create a new instance of each `RESTDataSource` subclass for each operation.** If multiple operations share a single data source instance, you might accidentally combine results from multiple operations. diff --git a/docs/source/data/resolvers.mdx b/docs/source/data/resolvers.mdx index 71c73839534..dcadf7bac40 100644 --- a/docs/source/data/resolvers.mdx +++ b/docs/source/data/resolvers.mdx @@ -381,7 +381,7 @@ Resolver functions are passed four arguments: `parent`, `args`, `context`, and ` ### The `context` argument -The `context` argument is useful for passing things that any resolver might need, like [authentication scope](https://blog.apollographql.com/authorization-in-graphql-452b1c402a9), [sources for fetching data](../fetching-rest/), database connections, and custom fetch functions. If you're using [dataloaders to batch requests](../fetching-rest/#using-with-dataloader) across resolvers, you can attach them to the `context` as well. +The `context` argument is useful for passing things that any resolver might need, like [authentication scope](https://blog.apollographql.com/authorization-in-graphql-452b1c402a9), [sources for fetching data](./fetching-rest/), database connections, and custom fetch functions. If you're using [dataloaders to batch requests](./fetching-rest/#using-with-dataloader) across resolvers, you can attach them to the `context` as well. **Resolvers should never destructively modify the `context` argument.** This ensures consistency across all resolvers and prevents unexpected errors. diff --git a/docs/source/security/authentication.mdx b/docs/source/security/authentication.mdx index e4820b47342..dabd07b7efd 100644 --- a/docs/source/security/authentication.mdx +++ b/docs/source/security/authentication.mdx @@ -166,7 +166,7 @@ As our server gets more complex, there will probably be multiple places in the s -As always, we recommend moving the actual data fetching and transformation logic from your resolvers to [data sources](../fetching-rest/) or model objects that each represent a concept from your application: `User`, `Post`, etc. This allows you to make your resolvers a thin routing layer, and put all of your business logic in one place. +As always, we recommend moving the actual data fetching and transformation logic from your resolvers to [data sources](../data/fetching-rest/) or model objects that each represent a concept from your application: `User`, `Post`, etc. This allows you to make your resolvers a thin routing layer, and put all of your business logic in one place. For example, a model file for `User` would include all the logic for operating on users, and might look something like this: From d10b97e5da4d74ebfa6058ef56ec359af6e83d48 Mon Sep 17 00:00:00 2001 From: Rose Koron Date: Thu, 25 Aug 2022 17:08:57 -0700 Subject: [PATCH 17/21] First round of feedback --- docs/source/data/fetching-data.mdx | 2 +- docs/source/data/fetching-rest.mdx | 16 +++++++++++----- docs/source/migration.mdx | 8 ++++---- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/docs/source/data/fetching-data.mdx b/docs/source/data/fetching-data.mdx index a5ada9597e8..b1ca89529b0 100644 --- a/docs/source/data/fetching-data.mdx +++ b/docs/source/data/fetching-data.mdx @@ -3,4 +3,4 @@ title: Fetching Data description: Fetching data from data sources besides REST --- - + diff --git a/docs/source/data/fetching-rest.mdx b/docs/source/data/fetching-rest.mdx index c00abb706b4..e898be6779b 100644 --- a/docs/source/data/fetching-rest.mdx +++ b/docs/source/data/fetching-rest.mdx @@ -3,6 +3,8 @@ title: Fetching from REST description: Using RESTDataSource to fetch data from REST APIs --- +> See the [`@apollo/datasource-rest` page](https://github.com/apollographql/datasource-rest) for the full details of the `RESTDataSource` API. + The `RESTDataSource` class helps you fetch data from REST APIs. The `RESTDataSource` class helps handle caching, deduplication, and errors while resolving operations. ```mermaid @@ -20,8 +22,6 @@ flowchart LR; > For more information about fetching from data sources other than a REST API, see [Fetching Data](./fetching-data). --> -> See the [`@apollo/datasource-rest` page](https://www.npmjs.com/package/@apollo/datasource-rest) for the full details of the `RESTDataSource` API. - ## Creating subclasses To get started, install the [`@apollo/datasource-rest` package](https://www.npmjs.com/package/@apollo/datasource-rest): @@ -39,11 +39,11 @@ import { RESTDataSource } from '@apollo/datasource-rest'; class MoviesAPI extends RESTDataSource { override baseURL = 'https://movies-api.example.com/'; - async getMovie(id):Promise { + async getMovie(id): Promise { return this.get(`movies/${encodeURIComponent(id)}`); } - async getMostViewedMovies(limit = '10'):Promise { + async getMostViewedMovies(limit = '10'): Promise { const data = await this.get('movies', { params: { per_page: limit, @@ -128,6 +128,8 @@ const resolvers = { > 📣 **New in Apollo Server 4**: Apollo Server no longer automatically provides its cache to data sources. [See here for more details](#datasources). +If you've set up [caching](../preformance/caching/#recommended-starting-usage) for your server, a data source can help store the results of past fetches for particular fields in your schema. + As shown in the above code snippet, by default, each `RESTDataSource` subclass accepts a `cache` argument (e.g., Apollo Server's default cache) to store the results of past fetches: ```ts disableCopy @@ -155,6 +157,8 @@ const { url } = await startStandaloneServer(server, { }); ``` +> Note that `RESTDataSource` only caches data for the fields specified by your server. To learn more about how to set up caching for your server, see [Server-side caching](../preformance/caching). + If your `RESTDataSource` subclass accepts multiple arguments, make sure you add a constructor function, like so: @@ -199,6 +203,7 @@ const { url } = await startStandaloneServer(server, { + When running multiple instances of your server, you should use a shared cache backend. This enables one server instance to use the cached result from _another_ instance. > If you want to configure or replace Apollo Sever's default cache, see [Configuring external caching](../performance/cache-backends) for more details. @@ -280,6 +285,8 @@ For all methods, the **third parameter** is an `init` object that enables you to ## Intercepting fetches +> **New in Apollo Server 4**: Apollo Server 4 now uses [`@apollo/fetcher`](../migration#apolloutilsfetcher-replaces-apollo-server-env) under-the-hood, so you can no longer directly use `Request` or `Headers` methods. + `RESTDataSource` includes a `willSendRequest` method that you can override to modify outgoing requests before they're sent. For example, you can use this method to add headers or query parameters. This method is most commonly used for authorization or other concerns that apply to all sent requests. Data sources also have access to the GraphQL operation context, which is useful for storing a user token or other relevant information. @@ -417,5 +424,4 @@ class PersonalizationAPI extends RESTDataSource { return this.progressLoader.load(id); } } - ``` diff --git a/docs/source/migration.mdx b/docs/source/migration.mdx index 11c96f13c2b..2de633e5330 100644 --- a/docs/source/migration.mdx +++ b/docs/source/migration.mdx @@ -1618,10 +1618,10 @@ new ApolloServer({ ## Renamed packages The following packages have been renamed in Apollo Server 4: - * `apollo-datasource-rest` is now [`@apollo/datasource-rest`](https://www.npmjs.com/package/@apollo/datasource-rest). The `@apollo/datasource-rest` package is compatible with both Apollo Server 3 and 4. - * `apollo-server-plugin-response-cache` is now [`@apollo/server-plugin-response-cache`](https://www.npmjs.com/package/@apollo/server-plugin-response-cache). - * `apollo-server-plugin-operation-registry` is now [`@apollo/server-plugin-operation-registry`](https://www.npmjs.com/package/@apollo/server-plugin-operation-registry). - * `apollo-reporting-protobuf` (an internal implementation detail for the usage reporting plugin) is now [`@apollo/usage-reporting-protobuf`](https://www.npmjs.com/package/@apollo/usage-reporting-protobuf). + - `apollo-datasource-rest` is now [`@apollo/datasource-rest`](https://www.npmjs.com/package/@apollo/datasource-rest). The `@apollo/datasource-rest` package removes an explicit dependency on `@apollo/server` and is compatible with both Apollo Server 3 and 4. + - `apollo-server-plugin-response-cache` is now [`@apollo/server-plugin-response-cache`](https://www.npmjs.com/package/@apollo/server-plugin-response-cache). + - `apollo-server-plugin-operation-registry` is now [`@apollo/server-plugin-operation-registry`](https://www.npmjs.com/package/@apollo/server-plugin-operation-registry). + - `apollo-reporting-protobuf` (an internal implementation detail for the usage reporting plugin) is now [`@apollo/usage-reporting-protobuf`](https://www.npmjs.com/package/@apollo/usage-reporting-protobuf). Note that once Apollo Server 4 is released, all actively maintained Apollo packages will start with `@apollo/`. This leaves the `apollo-` namespace open for community integration packages (e.g., `apollo-server-integration-fastify`). From 963710e0b0c864851c696b695794579b19bf6a33 Mon Sep 17 00:00:00 2001 From: Rose Koron Date: Thu, 25 Aug 2022 17:26:57 -0700 Subject: [PATCH 18/21] Second round feedback --- docs/source/data/fetching-rest.mdx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/source/data/fetching-rest.mdx b/docs/source/data/fetching-rest.mdx index e898be6779b..e85b7cc6448 100644 --- a/docs/source/data/fetching-rest.mdx +++ b/docs/source/data/fetching-rest.mdx @@ -239,7 +239,7 @@ class MoviesAPI extends RESTDataSource { async newMovie(movie) { return this.put( `movies`, // path - movie, // request body + { body: { movie } }, // request body ); } @@ -278,14 +278,12 @@ For all HTTP convenience methods, the **first parameter** is the relative path o The **second parameter** depends on the HTTP method: -- For HTTP methods with a request body (`post`, `put`, `patch`), the second parameter is an _object_ that contains the `method` `cacheOptions`, and request body (e.g., `{ body: { id: movie.id } }`). +- For HTTP methods with a request body (`post`, `put`, `patch`), the second parameter is an _object_ that contains the `method` `cacheOptions`, and request `body` (e.g., `{ body: { id: movie.id } }`). - For HTTP methods _without_ a request body, the second parameter is an object with keys and values corresponding to the request's query parameters. -For all methods, the **third parameter** is an `init` object that enables you to provide additional options (such as headers and referrers) to the `fetch` API that's used to send the request. For details, [see MDN's fetch docs](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters). - ## Intercepting fetches -> **New in Apollo Server 4**: Apollo Server 4 now uses [`@apollo/fetcher`](../migration#apolloutilsfetcher-replaces-apollo-server-env) under-the-hood, so you can no longer directly use `Request` or `Headers` methods. +> **New in Apollo Server 4**: Apollo Server 4 now uses [`@apollo/fetcher`](../migration#apolloutilsfetcher-replaces-apollo-server-env) under-the-hood for fetching, so hooks like `willSendRequest` no longer have direct access to `Request` or `Headers` methods. `RESTDataSource` includes a `willSendRequest` method that you can override to modify outgoing requests before they're sent. For example, you can use this method to add headers or query parameters. This method is most commonly used for authorization or other concerns that apply to all sent requests. From 301075abe67f7d35b70027d5fcb5eae8eed3e593 Mon Sep 17 00:00:00 2001 From: Rose Koron Date: Thu, 25 Aug 2022 18:07:07 -0700 Subject: [PATCH 19/21] Feedback --- docs/source/data/fetching-rest.mdx | 6 ++---- docs/source/migration.mdx | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/source/data/fetching-rest.mdx b/docs/source/data/fetching-rest.mdx index e85b7cc6448..e910058af82 100644 --- a/docs/source/data/fetching-rest.mdx +++ b/docs/source/data/fetching-rest.mdx @@ -104,7 +104,7 @@ console.log(`🚀 Server ready at ${url}`); Apollo Server calls [the `context` initialization](./resolvers/#the-context-argument) function for _every incoming operation_. This means: - For every operation, `context` returns an _object_ containing new instances of your `RESTDataSource` subclasses (in this case, `MoviesAPI` and `PersonalizationAPI`). -- The **`context` function should create a new instance of each `RESTDataSource` subclass for each operation.** If multiple operations share a single data source instance, you might accidentally combine results from multiple operations. +- The **`context` function should create a new instance of each `RESTDataSource` subclass for each operation.** Your resolvers can then access your data sources from the shared `context` object and use them to fetch data: @@ -128,7 +128,7 @@ const resolvers = { > 📣 **New in Apollo Server 4**: Apollo Server no longer automatically provides its cache to data sources. [See here for more details](#datasources). -If you've set up [caching](../preformance/caching/#recommended-starting-usage) for your server, a data source can help store the results of past fetches for particular fields in your schema. +The `RESTDataSource` class can cache results if the REST API it fetches from specifies caching headers in its HTTP responses (e.g., [`cache-control`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control)). As shown in the above code snippet, by default, each `RESTDataSource` subclass accepts a `cache` argument (e.g., Apollo Server's default cache) to store the results of past fetches: @@ -157,8 +157,6 @@ const { url } = await startStandaloneServer(server, { }); ``` -> Note that `RESTDataSource` only caches data for the fields specified by your server. To learn more about how to set up caching for your server, see [Server-side caching](../preformance/caching). - If your `RESTDataSource` subclass accepts multiple arguments, make sure you add a constructor function, like so: diff --git a/docs/source/migration.mdx b/docs/source/migration.mdx index 2de633e5330..57d8544ec47 100644 --- a/docs/source/migration.mdx +++ b/docs/source/migration.mdx @@ -1618,7 +1618,7 @@ new ApolloServer({ ## Renamed packages The following packages have been renamed in Apollo Server 4: - - `apollo-datasource-rest` is now [`@apollo/datasource-rest`](https://www.npmjs.com/package/@apollo/datasource-rest). The `@apollo/datasource-rest` package removes an explicit dependency on `@apollo/server` and is compatible with both Apollo Server 3 and 4. + - `apollo-datasource-rest` is now [`@apollo/datasource-rest`](https://www.npmjs.com/package/@apollo/datasource-rest). - `apollo-server-plugin-response-cache` is now [`@apollo/server-plugin-response-cache`](https://www.npmjs.com/package/@apollo/server-plugin-response-cache). - `apollo-server-plugin-operation-registry` is now [`@apollo/server-plugin-operation-registry`](https://www.npmjs.com/package/@apollo/server-plugin-operation-registry). - `apollo-reporting-protobuf` (an internal implementation detail for the usage reporting plugin) is now [`@apollo/usage-reporting-protobuf`](https://www.npmjs.com/package/@apollo/usage-reporting-protobuf). From e51c4e35bed51ace4e1f6eea53e5251d52ae1bd5 Mon Sep 17 00:00:00 2001 From: Rose M Koron <32436232+rkoron007@users.noreply.github.com> Date: Fri, 26 Aug 2022 09:15:58 -0700 Subject: [PATCH 20/21] Apply suggestions from code review Co-authored-by: David Glasser --- docs/source/data/fetching-rest.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/data/fetching-rest.mdx b/docs/source/data/fetching-rest.mdx index e910058af82..bdd1c739cb9 100644 --- a/docs/source/data/fetching-rest.mdx +++ b/docs/source/data/fetching-rest.mdx @@ -281,7 +281,7 @@ The **second parameter** depends on the HTTP method: ## Intercepting fetches -> **New in Apollo Server 4**: Apollo Server 4 now uses [`@apollo/fetcher`](../migration#apolloutilsfetcher-replaces-apollo-server-env) under-the-hood for fetching, so hooks like `willSendRequest` no longer have direct access to `Request` or `Headers` methods. +> **New in Apollo Server 4**: Apollo Server 4 now uses the [`@apollo/utils.fetcher`](../migration#apolloutilsfetcher-replaces-apollo-server-env) interface under the hood for fetching. This interface lets you choose your own implementation of the Fetch API. To ensure compatibility with all Fetch implementations, the request provided to hooks like `willSendRequest` is a plain JS object rather than a `Request` object with methods. `RESTDataSource` includes a `willSendRequest` method that you can override to modify outgoing requests before they're sent. For example, you can use this method to add headers or query parameters. This method is most commonly used for authorization or other concerns that apply to all sent requests. From db9b9e4a06d791483374361159e53a3403dfd761 Mon Sep 17 00:00:00 2001 From: Rose Koron Date: Fri, 26 Aug 2022 09:29:49 -0700 Subject: [PATCH 21/21] Update with third round of feedback --- docs/source/data/fetching-rest.mdx | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/docs/source/data/fetching-rest.mdx b/docs/source/data/fetching-rest.mdx index bdd1c739cb9..f6d66f3d806 100644 --- a/docs/source/data/fetching-rest.mdx +++ b/docs/source/data/fetching-rest.mdx @@ -229,7 +229,7 @@ class MoviesAPI extends RESTDataSource { async postMovie(movie) { return this.post( `movies`, // path - movie, // request body + { body: { movie } }, // request body ); } @@ -272,12 +272,21 @@ A "malicious" client could provide an `:id` of `1/characters` to target the dele ### Method parameters -For all HTTP convenience methods, the **first parameter** is the relative path of the endpoint you're sending the request to (e.g., `movies`). +For all HTTP convenience methods, the first parameter is the relative path of the endpoint you're sending the request to (e.g., `movies`). The second parameter is an object where you can set a request's `headers`, `params`, `cacheOptions`, and `body`: -The **second parameter** depends on the HTTP method: +```ts +class MoviesAPI extends RESTDataSource { + override baseURL = 'https://movies-api.example.com/'; -- For HTTP methods with a request body (`post`, `put`, `patch`), the second parameter is an _object_ that contains the `method` `cacheOptions`, and request `body` (e.g., `{ body: { id: movie.id } }`). -- For HTTP methods _without_ a request body, the second parameter is an object with keys and values corresponding to the request's query parameters. + // an example making an HTTP POST request + async postMovie(movie) { + return this.post( + `movies`, // path + { body: movie }, // request body + ); + } +} +``` ## Intercepting fetches