Advanced HTTP networking
Take full network control with Apollo Link
The Apollo Link library gives you fine-grained control of HTTP requests that are sent by Apollo Client. You can also use it to replace Apollo Client's networking layer with something completely custom, such as a WebSocket transport or mocked server data.
When using Apollo Link, you define network behavior as a collection of link objects that execute in sequence to control the flow of data. By default, Apollo Client uses Apollo Link's HttpLink
to send GraphQL queries over HTTP.
Apollo Link includes installable, premade links that support a variety of use cases. You can also create your own custom links.
Customizing request logic
The following example demonstrates adding a custom link to Apollo Client. This link adds an Authorization
header to every HTTP request before the HttpLink
sends it:
1import { ApolloClient, HttpLink, ApolloLink, InMemoryCache, concat } from '@apollo/client';
2
3const httpLink = new HttpLink({ uri: '/graphql' });
4
5const authMiddleware = new ApolloLink((operation, forward) => {
6 // add the authorization to the headers
7 operation.setContext(({ headers = {} }) => ({
8 headers: {
9 ...headers,
10 authorization: localStorage.getItem('token') || null,
11 }
12 }));
13
14 return forward(operation);
15})
16
17const client = new ApolloClient({
18 cache: new InMemoryCache(),
19 link: concat(authMiddleware, httpLink),
20});
This next example demonstrates providing multiple custom links in an array:
1import { ApolloClient, HttpLink, ApolloLink, InMemoryCache, from } from '@apollo/client';
2
3const httpLink = new HttpLink({ uri: '/graphql' });
4
5const authMiddleware = new ApolloLink((operation, forward) => {
6 // add the authorization to the headers
7 operation.setContext(({ headers = {} }) => ({
8 headers: {
9 ...headers,
10 authorization: localStorage.getItem('token') || null,
11 }
12 }));
13
14 return forward(operation);
15})
16
17const activityMiddleware = new ApolloLink((operation, forward) => {
18 // add the recent-activity custom header to the headers
19 operation.setContext(({ headers = {} }) => ({
20 headers: {
21 ...headers,
22 'recent-activity': localStorage.getItem('lastOnlineTime') || null,
23 }
24 }));
25
26 return forward(operation);
27})
28
29const client = new ApolloClient({
30 cache: new InMemoryCache(),
31 link: from([
32 authMiddleware,
33 activityMiddleware,
34 httpLink
35 ]),
36});
In the example above, the authMiddleware
link sets each request's Authorization
header, and the activityMiddleware
then sets each request's Recent-Activity
header. Finally, the HttpLink
sends the modified request.
Customizing response logic
You can use Apollo Link to customize Apollo Client's behavior whenever it receives a response from a request.
The following example demonstrates using @apollo/client/link/error
to handle network errors that are included in a response:
1import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
2import { onError } from '@apollo/client/link/error';
3
4import { logout } from './logout';
5
6const httpLink = new HttpLink({ uri: '/graphql' });
7
8const logoutLink = onError(({ networkError }) => {
9 if (networkError.statusCode === 401) logout();
10})
11
12const client = new ApolloClient({
13 cache: new InMemoryCache(),
14 link: logoutLink.concat(httpLink),
15});
In this example, the user is logged out of the application if the server returns a 401
code (unauthorized).
Modifying response data
You can create a custom link that edits or removes fields from response.data
. To do so, you call map
on the result of the link's forward(operation)
call. In the map
function, make the desired changes to response.data
and then return it:
1import { ApolloClient, InMemoryCache, HttpLink, ApolloLink } from '@apollo/client';
2
3const httpLink = new HttpLink({ uri: '/graphql' });
4
5const formatDateLink = new ApolloLink((operation, forward) => {
6 return forward(operation).map(response => {
7 if (response.data.date) {
8 response.data.date = new Date(response.data.date);
9 }
10 return response;
11 });
12});
13
14const client = new ApolloClient({
15 cache: new InMemoryCache(),
16 link: formatDateLink.concat(httpLink),
17});
In the above example, formatDateLink
changes a date
field to a Javascript Date object at the top level of each response.
Note that forward(operation).map(func)
doesn't support asynchronous execution of the func
mapping function. If you need to make asynchronous modifications, use the asyncMap
function from @apollo/client/utilities
, like so:
1import {
2 ApolloClient,
3 InMemoryCache,
4 HttpLink,
5 ApolloLink
6} from "@apollo/client";
7import { asyncMap } from "@apollo/client/utilities";
8
9import { usdToEur } from './currency';
10
11const httpLink = new HttpLink({ uri: '/graphql' });
12
13const usdToEurLink = new ApolloLink((operation, forward) => {
14 return asyncMap(forward(operation), async (response) => {
15 let data = response.data;
16 if (data.price && data.currency === "USD") {
17 data.price = await usdToEur(data.price);
18 data.currency = "EUR";
19 }
20 return response;
21 });
22});
23
24const client = new ApolloClient({
25 cache: new InMemoryCache(),
26 link: usdToEurLink.concat(httpLink)
27});
In the example above, usdToEurLink
uses asyncMap
to convert the response object's price
field from USD to EUR using an external API.
While this technique can be useful for modifying existing fields (or adding additional objects to lists within data
), adding new fields to data
won't work in most cases, because the operation document cannot be safely modified within the ApolloLink
request pipeline.
The HttpLink
object
Apollo Client uses HttpLink
to send GraphQL operations to a server over HTTP. The link supports both POST and GET requests, and it can modify HTTP options on a per-query basis. This comes in handy when implementing authentication, persisted queries, dynamic URIs, and other granular updates.
If your client doesn't have complex HTTP requirements, you probably don't need to create a custom instance of
HttpLink
. For details, see Basic HTTP networking.
Usage
1import { HttpLink } from "@apollo/client";
2
3const link = new HttpLink({ uri: "/graphql" });
Constructor options
The HttpLink
constructor accepts the following options:
Options | Description |
---|---|
uri | A string endpoint or function that resolves to the GraphQL server you want to execute operations against. (default: /graphql ) |
includeExtensions | If true , you can pass an extensions field to your GraphQL server. (default: false ) |
fetch | A fetch -compatible API for making a request. See Providing a fetch replacement for certain environments. |
headers | An object containing header names and values to include in each request. |
preserveHeaderCase | If true , header values retain their capitalization for non-http-spec-compliant servers. (default: false ) |
credentials | A string representing the credentials policy to use for the fetch call. (valid values: omit , include , same-origin ) |
fetchOptions | Include this to override the values of certain options that are provided to the fetch call. |
useGETForQueries | If true , HttpLink uses GET requests instead of POST requests to execute query operations (but not mutation operations). (default: false ) |
print | A function to customize AST formatting in requests. See Overriding the default print function. |
Providing a fetch
replacement for certain environments
HttpLink
requires that fetch
is present in your runtime environment. This is the case for React Native and most modern browsers. If you're targeting an environment that doesn't include fetch
(such as older browsers or the server), you need to pass your own fetch
to HttpLink
via its constructor options. We recommend using cross-fetch
for older browsers and Node.
Overriding the default print
function
The print
option is useful for customizing the way DocumentNode
objects are transformed back into strings before they are sent over the network. If no custom print
function is provided, the GraphQL print
function will be used. A custom print
function should accept an ASTNode
(typically a DocumentNode
) and the original print
function as arguments, and return a string. This option can be used with stripIgnoredCharacters
to remove whitespace from queries:
1import { ASTNode, stripIgnoredCharacters } from 'graphql';
2
3const httpLink = new HttpLink({
4 uri: '/graphql',
5 print(
6 ast: ASTNode,
7 originalPrint: (ast: ASTNode) => string,
8 ) {
9 return stripIgnoredCharacters(originalPrint(ast));
10 },
11});
Overriding options
You can override most HttpLink
constructor options on an operation-by-operation basis by modifying the context
object for the operation. For example, you can set the headers
field on the context
to pass custom headers for a particular operation. The context
also supports the credentials
field for defining credentials policy, uri
for changing the endpoint dynamically, and fetchOptions
to allow generic fetch overrides (i.e., method: "GET"
).
Note that if you set fetchOptions.method
to GET
, HttpLink
follows the standard GraphQL HTTP GET encoding. The query, variables, operation name, and extensions are passed as query parameters instead of in the HTTP request body (because there isn't one). If you to continue to send mutations as non-idempotent POST
requests, set the top-level useGETForQueries
option to true
instead of setting fetchOptions.method
to GET
.
HttpLink
also attaches the response from the fetch
operation to the context as response
, so you can access it in another link.
Context options:
Option | Description |
---|---|
headers | An object containing header names and values to include in each request. |
credentials | A string representing the credentials policy to use for the fetch call. (valid values: omit , include , same-origin ) |
uri | A string endpoint or function that resolves to the GraphQL server you want to execute operations against. |
fetchOptions | Any overrides of the fetch options argument to pass to the fetch call. |
response | The raw response from the fetch request after it is made. |
http | An object that lets you control fine-grained aspects of HttpLink itself, such as persisted queries (see below). |
The following example shows how to use the context
to pass a special header for a single query:
1import { ApolloClient, InMemoryCache } from "@apollo/client";
2
3const client = new ApolloClient({
4 cache: new InMemoryCache(),
5 uri: "/graphql"
6});
7
8client.query({
9 query: MY_QUERY,
10 context: {
11 // example of setting the headers with context per operation
12 headers: {
13 special: "Special header value"
14 }
15 }
16});
Custom fetching
HttpLink
's fetch
option can be used to wire in custom networking. This is useful if you want to modify the request based on calculated headers, or calculate the URI based on an operation. For example:
Custom auth:
1const customFetch = (uri, options) => {
2 const { header } = Hawk.client.header(
3 "http://example.com:8000/resource/1?b=1&a=2",
4 "POST",
5 { credentials: credentials, ext: "some-app-data" }
6 );
7 options.headers.Authorization = header;
8 return fetch(uri, options);
9};
10
11const link = new HttpLink({ fetch: customFetch });
Dynamic URI:
1const customFetch = (uri, options) => {
2 const { operationName } = JSON.parse(options.body);
3 return fetch(`${uri}/graph/graphql?opname=${operationName}`, options);
4};
5
6const link = new HttpLink({ fetch: customFetch });
Using other links
Apollo Link includes many links for specialized use cases, such as the WebSocketLink
for communicating over WebSocket and the BatchHttpLink
for combining multiple GraphQL operations in a single HTTP request.
To learn more about what's available, see the Apollo Link API documentation.