[go: up one dir, main page]

Apollo Link overview

Customize Apollo Client's data flow


If your application only needs to send conventional HTTP-based requests to a GraphQL server, you probably don't need to use the Apollo Link API. See Basic HTTP networking.

The Apollo Link library helps you customize the flow of data between Apollo Client and your GraphQL server. You can define your client's network behavior as a chain of link objects that execute in a sequence:

Each link should represent either a self-contained modification to a GraphQL operation or a side effect (such as logging).

In the above diagram:

  1. The first link might log the details of the operation for debugging purposes.

  2. The second link might add an HTTP header to the outgoing operation request for authentication purposes.

  3. The final (terminating) link sends the operation to its destination (usually a GraphQL server over HTTP).

  4. The server's response is passed back up each link in reverse order, enabling links to modify the response or take other actions before the data is cached.

By default, Apollo Client uses Apollo Link's HttpLink to send GraphQL operations to a remote server over HTTP. Apollo Client takes care of creating this default link, and it covers many use cases without requiring additional customization.

To extend or replace this default networking behavior, you can define custom links and specify their order of execution in the ApolloClient constructor.

The example below demonstrates a basic link chain with two Apollo-provided links:

  • An onError link that checks for errors in the server's response. It logs the details of whichever error(s) it finds.

  • An HttpLink that sends each GraphQL operation to your server.

Note that if you provide a link chain to the ApolloClient constructor, you don't provide the uri option. Instead, you provide your server's URL to your HttpLink.

Click to expand example
JavaScript
index.js
1import { ApolloClient, InMemoryCache, HttpLink, from } from "@apollo/client";
2import { onError } from "@apollo/client/link/error";
3
4const httpLink = new HttpLink({
5  uri: "http://localhost:4000/graphql"
6});
7
8const errorLink = onError(({ graphQLErrors, networkError }) => {
9  if (graphQLErrors)
10    graphQLErrors.forEach(({ message, locations, path }) =>
11      console.log(
12        `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`,
13      ),
14    );
15
16  if (networkError) console.log(`[Network error]: ${networkError}`);
17});
18
19// If you provide a link chain to ApolloClient, you
20// don't provide the `uri` option.
21const client = new ApolloClient({
22  // The `from` function combines an array of individual links
23  // into a link chain
24  link: from([errorLink, httpLink]),
25  cache: new InMemoryCache()
26});

A link object is an instance of the ApolloLink class (or a subclass of it). Each link must define a method named request, which is known as the link's request handler. You can provide this method's definition to the ApolloLink constructor.

Example

The following custom link defines a request handler that adds a GraphQL operation's approximate start time to the operation's context:

JavaScript
1import { ApolloLink } from '@apollo/client';
2
3const timeStartLink = new ApolloLink((operation, forward) => {
4  operation.setContext({ start: new Date() });
5  return forward(operation);
6});

This request handler then calls return forward(operation), which is the syntax for calling the next link down the chain.

The request handler

Every link must define a request method, also known as its request handler. This method is passed the following arguments:

  • operation: The GraphQL operation that's being passed through the link.

  • forward: A function for executing the next link in the chain (unless this is a terminating link).

Whenever Apollo Client prepares to execute a GraphQL operation, it calls the request handler of the first link in the chain. Each link is responsible for executing its logic and then passing execution to the next link by calling the forward function and returning its result.

The Operation object

The Operation object includes the following fields:

NameDescription
queryA DocumentNode (parsed GraphQL operation) that describes the operation taking place.
variablesA map of GraphQL variables being sent with the operation.
operationNameA string name of the query if it is named, otherwise null.
extensionsA map to store extensions data to be sent to the server.
getContextA function to return the context of the request. This context can be used by links to determine which actions to perform. See Managing context.
setContextA function that takes either a new context object, or a function which takes in the previous context and returns a new one. See Managing context.

The forward function

When your custom link's request handler is done executing its logic, it should return a call to the forward function that's passed to it (unless it's the chain's terminating link). Calling return forward(operation) passes execution along to the next link in the chain.

If a non-terminating custom link's request handler doesn't return forward(operation), the link chain ends and the associated GraphQL operation is not executed.

The forward function's return type is an Observable provided by the zen-observable library. See the zen-observable documentation for details.

Handling a response

When your GraphQL server responds with an operation result, that result is passed back up through each link in your link chain before it's cached:

Each link can execute logic while the result is being passed back by modifying their request handler's return statement, like so:

JavaScript
1// BEFORE (NO INTERACTION)
2return forward(operation);
3
4// AFTER
5return forward(operation).map((data) => {
6  // ...modify result as desired here...
7  return data;
8});

Whatever the function provided to map returns is passed to the next link up the chain.

You can also perform logic here that has nothing to do with the result. This request handler uses the request context to estimate the round-trip time for each operation:

JavaScript
1import { ApolloLink } from '@apollo/client';
2
3const roundTripLink = new ApolloLink((operation, forward) => {
4  // Called before operation is sent to server
5  operation.setContext({ start: new Date() });
6
7  return forward(operation).map((data) => {
8    // Called after server responds
9    const time = new Date() - operation.getContext().start;
10    console.log(`Operation ${operation.operationName} took ${time} to complete`);
11    return data;
12  });
13});

Each link represents either a self-contained modification to a GraphQL operation or a side effect (such as logging). By composing these links into a chain, you can create an arbitrarily complex model for your client's data flow.

There are two forms of link composition: additive and directional.

Note that no matter how your chain branches, each branch always ends in a terminating link.

The terminating link is the last link in a link chain. Instead of calling the forward function, the terminating link is responsible for sending your composed GraphQL operation to the destination that executes it (usually a GraphQL server) and returning an ExecutionResult.

HttpLink and BatchHttpLink are both examples of terminating links.

Additive composition

If you have a collection of two or more links that should always be executed in serial order, use the ApolloLink.from helper method to combine those links into a single link, like so:

JavaScript
1import { from, HttpLink } from '@apollo/client';
2import { RetryLink } from '@apollo/client/link/retry';
3import MyAuthLink from '../auth';
4
5const additiveLink = from([
6  new RetryLink(),
7  new MyAuthLink(),
8  new HttpLink({ uri: 'http://localhost:4000/graphql' })
9]);

Directional composition

You might want your link chain's execution to branch, depending on the details of the operation being performed.

To support this, the @apollo/client library provides a split function that lets you use one of two different Links, according to the result of a boolean check. You can also use the split method of an ApolloLink instance.

NameDescription
testA function that takes in the current Operation and returns either true or false depending on its details.
leftThe link to execute next if the test function returns true.
rightAn optional link to execute next if the test function returns false. If this is not provided, the request handler's forward parameter is used.

In the following example, a RetryLink passes execution along to one of two different HttpLinks depending on the associated context's version:

JavaScript
1import { ApolloLink, HttpLink } from '@apollo/client';
2import { RetryLink } from '@apollo/client/link/retry';
3
4const directionalLink = new RetryLink().split(
5  (operation) => operation.getContext().version === 1,
6  new HttpLink({ uri: 'http://localhost:4000/v1/graphql' }),
7  new HttpLink({ uri: 'http://localhost:4000/v2/graphql' })
8);

Other uses for the split method include:

  • Customizing the number of allowed retry attempts depending on the operation type

  • Using different transport methods depending on the operation type (such as HTTP for queries and WebSocket for subscriptions)

  • Customizing logic depending on whether a user is logged in

In the following example, all subscription operations are sent to GraphQLWsLink, with all other operations sent to HttpLink:

JavaScript
1import { split, HttpLink } from '@apollo/client';
2import { getMainDefinition } from '@apollo/client/utilities';
3import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
4import { createClient } from 'graphql-ws';
5
6export const link = split(
7  ({ query }) => {
8    const definition = getMainDefinition(query);
9    return (
10      definition.kind === 'OperationDefinition' &&
11      definition.operation === 'subscription'
12    );
13  },
14  new GraphQLWsLink(createClient({ url: 'ws://localhost:3000/subscriptions' })),
15  new HttpLink({ uri: 'http://localhost:4000/graphql' })
16);

Providing to Apollo Client

After you finish composing your entire link chain, you provide the resulting link to the constructor of ApolloClient, like so:

JavaScript
1import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
2import { RetryLink } from '@apollo/client/link/retry';
3
4const directionalLink = new RetryLink().split(
5  (operation) => operation.getContext().version === 1,
6  new HttpLink({ uri: "http://localhost:4000/v1/graphql" }),
7  new HttpLink({ uri: "http://localhost:4000/v2/graphql" })
8);
9
10const client = new ApolloClient({
11  cache: new InMemoryCache(),
12  link: directionalLink
13});

Most links perform the same logic for every operation they process, and they don't need to know anything about operations that have been executed previously. These links are stateless.

You can define the request handler for a stateless link in the constructor of an ApolloLink object, like so:

JavaScript
1import { ApolloLink } from '@apollo/client';
2
3const consoleLink = new ApolloLink((operation, forward) => {
4  console.log(`starting request for ${operation.operationName}`);
5  return forward(operation).map((data) => {
6    console.log(`ending request for ${operation.operationName}`);
7    return data;
8  })
9})

Stateless links are great for implementing middleware and even network requests. The following link adds an Authorization header to every outgoing request:

JavaScript
1import { ApolloLink } from '@apollo/client';
2
3const authLink = new ApolloLink((operation, forward) => {
4  operation.setContext(({ headers }) => ({ headers: {
5    authorization: Auth.userId(), // however you get your token
6    ...headers
7  }}));
8  return forward(operation);
9});

This style of link also composes well for customization using a function:

JavaScript
1import { ApolloLink } from '@apollo/client';
2
3const reportErrors = (errorCallback) => new ApolloLink((operation, forward) => {
4  return new Observable((observer) => {
5    const observable = forward(operation);
6    const subscription = observable.subscribe({
7      next(value) {
8        observer.next(value);
9      },
10      error(networkError) {
11        errorCallback({ networkError, operation });
12        observer.error(networkError);
13      },
14      complete() {
15        observer.complete();
16      },
17    });
18
19    return () => subscription.unsubscribe();
20  });
21});
22
23const link = reportErrors(console.error);

You can also create a stateless link by extending the ApolloLink class and overwriting its constructor and request handler. For example, here's the same reportErrors link written as an extension of ApolloLink:

JavaScript
1import { ApolloLink } from '@apollo/client';
2
3class ReportErrorLink extends ApolloLink {
4  constructor(errorCallback) {
5    super();
6    this.errorCallback = errorCallback;
7  }
8  request(operation, forward) {
9    const observable = forward(operation);
10    // errors will be sent to the errorCallback
11    observable.subscribe({ error: this.errorCallback })
12    return observable;
13  }
14}
15
16const link = new ReportErrorLink(console.error);

When it's useful, links can maintain state between operations. These links are stateful.

Stateful links are usually defined as subclasses of ApolloLink. They override the constructor of ApolloLink and implement a request function with the same signature as a stateless link. For example:

JavaScript
1import { ApolloLink } from '@apollo/client';
2
3class OperationCountLink extends ApolloLink {
4  constructor() {
5    super();
6    this.operationCount = 0;
7  }
8  request(operation, forward) {
9    this.operationCount += 1;
10    return forward(operation);
11  }
12}
13
14const link = new OperationCountLink();

This stateful link maintains a counter called operationCount as an instance variable. Every time a request is passed through the link, operationCount is incremented.

Managing context

As an operation moves along your link chain, it maintains a context that each link can read and modify. This allows links to pass metadata along the chain that other links use in their execution logic.

  • Obtain the current context object by calling operation.getContext().

  • Modify the context object and then write it back with operation.setContext(newContext) or operation.setContext((prevContext) => newContext).

Note that this context is not included in the terminating link's request to the GraphQL server or other destination.

Here's an example:

JavaScript
1import { ApolloLink, from } from '@apollo/client';
2
3const timeStartLink = new ApolloLink((operation, forward) => {
4  operation.setContext({ start: new Date() });
5  return forward(operation);
6});
7
8const logTimeLink = new ApolloLink((operation, forward) => {
9  return forward(operation).map((data) => {
10    // data from a previous link
11    const time = new Date() - operation.getContext().start;
12    console.log(`operation ${operation.operationName} took ${time} to complete`);
13    return data;
14  })
15});
16
17const additiveLink = from([
18  timeStartLink,
19  logTimeLink
20]);

This example defines two links, timeStartLink and logTimeLink. The timeStartLink assigns the current time to the context's start field. When the operation completes, the logTimeLink then subtracts the value of start from the current time to determine the total duration of the operation.

You can set the context object's initial value for a particular operation by providing the context parameter to the useQuery hook (or useMutation, useSubscription, etc.).

Feedback

Edit on GitHub

Forums