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:
The first link might log the details of the operation for debugging purposes.
The second link might add an HTTP header to the outgoing operation request for authentication purposes.
The final (terminating) link sends the operation to its destination (usually a GraphQL server over HTTP).
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.
Your first link chain
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.This is the chain's terminating link.
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
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});
Creating a custom link
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:
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.For details, see The
Operation
object.
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:
Name | Description |
---|---|
query | A DocumentNode (parsed GraphQL operation) that describes the operation taking place. |
variables | A map of GraphQL variables being sent with the operation. |
operationName | A string name of the query if it is named, otherwise null . |
extensions | A map to store extensions data to be sent to the server. |
getContext | A function to return the context of the request. This context can be used by links to determine which actions to perform. See Managing context. |
setContext | A 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:
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:
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});
Composing a link chain
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.
Additive composition involves combining a set of links into a serially executed chain:
Directional composition involves branching to one of two links, depending on the details of an operation:
Note that no matter how your chain branches, each branch always ends in a terminating link.
The 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:
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 Link
s, according to the result of a boolean check. You can also use the split
method of an ApolloLink
instance.
Name | Description |
---|---|
test | A function that takes in the current Operation and returns either true or false depending on its details. |
left | The link to execute next if the test function returns true . |
right | An 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 HttpLink
s depending on the associated context's version
:
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
:
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:
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});
Link types
Stateless links
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:
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:
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:
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);
Extending ApolloLink
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
:
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);
Stateful links
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:
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)
oroperation.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:
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.).