Docs
Launch GraphOS Studio

Apollo Link overview

Customize Apollo Client's data flow


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

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

Apollo Client
Initiated
down
down
up
up
Completed
Request
Response
GraphQL operation
Link
Link
Terminating Link
GraphQL server

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

In the above diagram:

  1. The first link might log the details of the for debugging purposes.
  2. The second link might add an HTTP header to the outgoing request for authentication purposes.
  3. The final (terminating) link sends the to its destination (usually a 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, uses 's HttpLink to send to a remote server over HTTP. 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 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.

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 's approximate start time to the operation's context:

import { ApolloLink } from '@apollo/client';
const timeStartLink = new ApolloLink((operation, forward) => {
operation.setContext({ start: new Date() });
return forward(operation);
});

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 :

  • operation: The 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 prepares to execute a , 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 :

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 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 responds with an result, that result is passed back up through each link in your link chain before it's cached:

Apollo Client
Initiated
down
down
up
up
Completed
Request
Response
GraphQL operation
Link
Link
Terminating Link
GraphQL server

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

// BEFORE (NO INTERACTION)
return forward(operation);
// AFTER
return forward(operation).map((data) => {
// ...modify result as desired here...
return data;
});

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 :

import { ApolloLink } from '@apollo/client';
const roundTripLink = new ApolloLink((operation, forward) => {
// Called before operation is sent to server
operation.setContext({ start: new Date() });
return forward(operation).map((data) => {
// Called after server responds
const time = new Date() - operation.getContext().start;
console.log(`Operation ${operation.operationName} took ${time} to complete`);
return data;
});
});

Each link represents either a self-contained modification to a 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 : additive and directional.

  • Additive composition involves combining a set of links into a serially executed chain:

    Link
    Link
    Terminating Link

  • Directional composition involves branching to one of two links, depending on the details of an :

    Link
    Link
    Terminating Link
    Terminating Link

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 to the destination that executes it (usually a ) 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:

import { from, HttpLink } from '@apollo/client';
import { RetryLink } from '@apollo/client/link/retry';
import MyAuthLink from '../auth';
const additiveLink = from([
new RetryLink(),
new MyAuthLink(),
new HttpLink({ uri: 'http://localhost:4000/graphql' })
]);

Directional composition

You might want your link chain's execution to branch, depending on the details of the 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:

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

Other uses for the split method include:

  • Customizing the number of allowed retry attempts depending on the type
  • Using different transport methods depending on the type (such as HTTP for queries and WebSocket for )
  • Customizing logic depending on whether a user is logged in

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

import { split, HttpLink } from '@apollo/client';
import { getMainDefinition } from '@apollo/client/utilities';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
export const link = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
new GraphQLWsLink(createClient({ url: 'ws://localhost:3000/subscriptions' })),
new HttpLink({ uri: 'http://localhost:4000/graphql' })
);

Providing to Apollo Client

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

import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
import { RetryLink } from '@apollo/client/link/retry';
const directionalLink = new RetryLink().split(
(operation) => operation.getContext().version === 1,
new HttpLink({ uri: "http://localhost:4000/v1/graphql" }),
new HttpLink({ uri: "http://localhost:4000/v2/graphql" })
);
const client = new ApolloClient({
cache: new InMemoryCache(),
link: directionalLink
});

Most links perform the same logic for every 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:

import { ApolloLink } from '@apollo/client';
const consoleLink = new ApolloLink((operation, forward) => {
console.log(`starting request for ${operation.operationName}`);
return forward(operation).map((data) => {
console.log(`ending request for ${operation.operationName}`);
return data;
})
})

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

import { ApolloLink } from '@apollo/client';
const authLink = new ApolloLink((operation, forward) => {
operation.setContext(({ headers }) => ({ headers: {
authorization: Auth.userId(), // however you get your token
...headers
}}));
return forward(operation);
});

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

import { ApolloLink } from '@apollo/client';
const reportErrors = (errorCallback) => new ApolloLink((operation, forward) => {
return new Observable((observer) => {
const observable = forward(operation);
const subscription = observable.subscribe({
next(value) {
observer.next(value);
},
error(networkError) {
errorCallback({ networkError, operation });
observer.error(networkError);
},
complete() {
observer.complete();
},
});
return () => subscription.unsubscribe();
});
});
const 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:

import { ApolloLink } from '@apollo/client';
class ReportErrorLink extends ApolloLink {
constructor(errorCallback) {
super();
this.errorCallback = errorCallback;
}
request(operation, forward) {
const observable = forward(operation);
// errors will be sent to the errorCallback
observable.subscribe({ error: this.errorCallback })
return observable;
}
}
const link = new ReportErrorLink(console.error);

When it's useful, links can maintain state between . 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:

import { ApolloLink } from '@apollo/client';
class OperationCountLink extends ApolloLink {
constructor() {
super();
this.operationCount = 0;
}
request(operation, forward) {
this.operationCount += 1;
return forward(operation);
}
}
const link = new OperationCountLink();

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

Managing context

As an 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 or other destination.

Here's an example:

import { ApolloLink, from } from '@apollo/client';
const timeStartLink = new ApolloLink((operation, forward) => {
operation.setContext({ start: new Date() });
return forward(operation);
});
const logTimeLink = new ApolloLink((operation, forward) => {
return forward(operation).map((data) => {
// data from a previous link
const time = new Date() - operation.getContext().start;
console.log(`operation ${operation.operationName} took ${time} to complete`);
return data;
})
});
const additiveLink = from([
timeStartLink,
logTimeLink
]);

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

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

Previous
HOC (deprecated)
Next
HTTP
Edit on GitHubEditForumsDiscord

© 2024 Apollo Graph Inc.

Privacy Policy

Company