Feb 8, 2024

Apollo Client Network Interceptors in React Native

Unlock the full potential of Apollo Client in Expo. Explore network interceptors for seamless GraphQL interactions in your React Native applications in our latest article.

Author

Amritansh Kumar MishraSenior Software Engineer - II

Subject Matter Expert

Jajala Trived KumarSenior Software Engineer - III
Apollo Client Network Interceptors in React Native


Introduction

This guide will unravel the wonders of Apollo Client's interceptor and demonstrate how effortlessly it can be integrated into your Expo project. Whether you are a seasoned developer or just embarked on your React Native journey with Expo, get ready to add a touch of magic to your GraphQL interactions.

What is Apollo Client Interceptor, Anyway?

Apollo Client's network interceptor is like having a personal assistant for your network requests. It allows you to intervene at various stages of the data-fetching process, enabling you to customize, log, authenticate, or gracefully handle errors. This is about making requests and crafting an experience tailored to your application's unique needs.


Basic Setup of Apollo Client in an Expo Project

1. Setup

Create a new expo project by running the following command:

npm create gluestack

We are using the gluestack framework here to create an expo project. You can choose to create a normal expo app by running:

npx create-expo-app my-app

2. Install Dependencies

Applications that use Apollo Client require two top-level dependencies:

  • @apollo/client: This single package contains virtually everything you need to set up Apollo Client. It includes the in-memory cache, local state management, error handling, and a React-based view layer.
  • graphql: This package provides logic for parsing GraphQL queries.

Run the following command to install both of these packages:

npm install @apollo/client graphql

3. Initialize Apollo Client

Next, we will initialize ApolloClient, passing its constructor a configuration object with the uri and cache fields:

//client.ts
import { ApolloClient, InMemoryCache } from '@apollo/client'
const client = new ApolloClient({
  uri: '<https://graphqlzero.almansi.me/api>',
  cache: new InMemoryCache(),
})
export default client

4. Add Provider

Add provider by running the following command:

//App.tsx
<ApolloProvider client={client}>
	<GluestackUIProvider config={config}>
	 // this will only be there if you are using gluestack
<Home />
</GluestackUIProvider>
</ApolloProvider>

5. Usage of Apollo Network Interceptor

Implement Authentication, error handling, and debouncing globally.

I. Authentication

Run the following command:

import {
  ApolloClient,
  InMemoryCache,
  createHttpLink,
  from
} from '@apollo/client'
import { setContext } from '@apollo/client/link/context'

const httpLink = createHttpLink({
  uri: `<BASE_URL>`,
// This option instructs the browser to include cookies and other credentials in cross-origin requests. It's often used when the GraphQL server requires authentication.
  credentials: 'include',
})

const authLink = setContext(async (_, { headers }) => {
  const token = `YOUR_AUTH_TOKEN`
  if (token) {
    return {
      headers: {
        ...headers,
        authorization: `Bearer ${token}`,
      },
    }
  }
  return {
    headers: {
      ...headers,
    },
  }
})

const client = new ApolloClient({
  cache: new InMemoryCache(),
  link: from([
    authLink,
    httpLink,
  ]),
  connectToDevTools: true,
  
})

export default client

II. Error-handling

Apollo Client stands ready to assist you in gracefully handling errors that may arise during GraphQL operations, ensuring a seamless user experience. It provides tools to effectively address different error types, empowering you to display informative messages and take appropriate actions when issues occur.

Types Of Errors

Executing GraphQL operations on a remote server can result in two types of error:

  • GraphQL Errors
  • Network Errors

If the query produces one or more errors, this object contains either an array of graphQLErrors or a single networkError. Otherwise, this value is undefined.

GraphQL Errors

GraphQL errors are returned directly from the GraphQL server. These errors could manifest for a multitude of reasons, including validation failures, authentication issues, or permission problems.

  • Syntax errors (e.g., a query was malformed)
  • Validation errors (e.g., a query included a schema field that doesn't exist)
  • Resolver errors (e.g., an error occurred while attempting to populate a query field)

To provide insights into GraphQL-specific issues, Apollo Client meticulously gathers any GraphQL errors and presents them within the error.graphQLErrors array within your operation hooks. This detailed information empowers you to pinpoint and resolve problems originating from the GraphQL server itself. Notably, Apollo Server signals complete operation failures with 4xx status codes, while partial data responses accompanied by resolver errors are indicated with a 200 status code. This distinction allows you to tailor your error-handling strategies accordingly.

Example error response

{
  "errors": [
    {
      "message": "Cannot query field \\"nonexistentField\\" on type \\"Query\\".",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "extensions": {
        "code": "GRAPHQL_VALIDATION_FAILED",
        "exception": {
          "stacktrace": [
            "GraphQLError: Cannot query field \\"nonexistentField\\" on type \\"Query\\".",
            "...additional lines..."
          ]
        }
      }
    }
  ],
  "data": null
}

Network Errors

To stay informed about network hiccups, Apollo Client diligently captures any network errors and surfaces them within the error.networkError field of your operation hooks, such as useQuery. This allows you to quickly identify and address communication issues with your GraphQL server.

Error Policies:

none: If the response includes GraphQL errors, they are returned on error.graphQLErrors and the response data is set to undefined even if the server returns data in its response. This means network errors and GraphQL errors result in a similar response shape. This is the default error policy.

ignore: graphQLErrors are ignored (error.graphQLErrors is not populated), and any returned data is cached and rendered as if no errors occurred.

all: Both data and error.graphQLErrors are populated, enabling you to render both partial results and error information.

That’s enough theory. Let us get to it with an example:

// above imports
import { onError } from '@apollo/client/link/error'
import { getNewToken } from '@helpers/functions'

const httpLink = `same as above`
const authLink = `same as above`
const errorLink = onError(({ graphQLErrors, networkError, operation, forward }) => {
  if (graphQLErrors) {
    for (const err of graphQLErrors) {
      console.log(
        `[GraphQL error]: Message: ${err?.message}, code:     ${err?.extensions?.code}, Path: ${err?.extensions?.path}`,
      )
      switch (err?.extensions?.code) {
// When an the jwt is expired we have error below error message, you might need to modify the case based on your backend error code
        case 'invalid-jwt': {
          // Modify the operation context with a new token
          const prevHeaders = operation.getContext().headers
          return new Observable((observer) => {
// getNewToken will fetch refresh token
            getNewToken()
              .then((token) => {
                if (token) {
                  operation.setContext({
                    headers: {
                      ...prevHeaders,
                      authorization: token,
                    },
                  })
                }
              })
              .then(() => {
// we are again calling the same api with updated token
                const subscriber = {
                  next: observer.next.bind(observer),
                  error: observer.error.bind(observer),
                  complete: observer.complete.bind(observer),
                }

                // Retry last failed request
                forward(operation).subscribe(subscriber)
              })
              .catch((error) => {
                console.log('LOGGING OUT :>> ', error)
			// No refresh token available, hence forced logout
              })
          })
        }
      }
    }
  }
  if (networkError) {
    console.log(`[Network error]: ${networkError}`)
    // you can handle the network errors according to your application needs
  }
})
const client = new ApolloClient({
  cache: new InMemoryCache(),
  link: from([
    authLink,
    errorLink,
    httpLink,
  ]),
  connectToDevTools: true,
  defaultOptions: {
    mutate: {
      errorPolicy: 'all',
    },
    query: {
      errorPolicy: 'all',
    },
    watchQuery: {
      errorPolicy: 'all',
    },
  },
}
export default client

III. Debouncing

Adding debounce in the Apollo Client network layer brings several benefits, especially when dealing with scenarios where frequent or rapid requests can lead to inefficiencies or unnecessary load on the server. Here are some theoretical points explaining the advantages of incorporating debounce in the Apollo Client network layer:

  • Optimized Server Resources
  • Improved User Experience
  • Avoidance of Unnecessary Rendering
  • Enhanced Stability in Unstable Network Conditions

We have implemented debounce globally using the *apollo-link-debounce* library. You can check more information here.

// above imports
import DebounceLink from 'apollo-link-debounce'

const DEFAULT_DEBOUNCE_TIMEOUT = 300

const httpLink = `same as above`
const authLink = `same as above`
const errorLink = `same as above`
const debounceLink = new ApolloLink((operation, forward) => {
  return new Observable((observer) => {
    const { operationName } = operation
    if (
      operationName !== 'ANY_API'
      // you can use this to remove any particular api that needs to be called multiple times
    ) {
      operation.setContext((res: any) => {
        return {
          ...res,
          debounceKey: operationName,
        }
      })
    }
    forward(operation).subscribe({
      next: observer.next.bind(observer),
      error: observer.error.bind(observer),
      complete: observer.complete.bind(observer),
    })
  })
})

const client = new ApolloClient({
 cache: new InMemoryCache(),
  link: from([
    authLink,
    debounceLink,
    new DebounceLink(DEFAULT_DEBOUNCE_TIMEOUT),
    errorLink,
    httpLink,
  ]),
  connectToDevTools: true,
  defaultOptions: {
    // same as above
  },
}
export default client


Summing Up

Apollo Client's network interceptor in React Native provides a flexible way to intercept and modify network requests and responses. Whether you need to log requests, add authentication headers, or modify payload such as we have implemented debounce part, or handle errors, network interceptors offer a powerful mechanism to customize your GraphQL client behavior. By leveraging these interceptors effectively, you can enhance the reliability, security, and maintainability of your React Native applications that use Apollo Client for GraphQL operations. You streamline development and maintenance by applying these global strategies across your Apollo Client network layer. Consistent authentication, error handling, and debouncing practices eliminate the need for redundant code, making your codebase cleaner, more maintainable, and less prone to bugs.

Subscribe to Our Newsletter

RELATED ARTICLES

More from the engineering frontline.

Dive deep into our research and insights on design, development, and the impact of various trends to businesses.
The Bug That Doesn't Show Up in Code Review: Why Your Flutter Web App Reloads on Safari

Aug 19, 2026

The Bug That Doesn't Show Up in Code Review: Why Your Flutter Web App Reloads on Safari
A real-world look at how oversized images can trigger Safari reloads and iOS crashes in Flutter apps and how smarter image decoding prevents them.
From Prompting to Process: What Changed When Flutter Shipped Agent Skills

Aug 19, 2026

From Prompting to Process: What Changed When Flutter Shipped Agent Skills
This blog explores how Flutter Agent Skills improve AI-assisted development by combining official framework workflows with project-specific guidance for more consistent development.
Why Everything Your AI Builds Looks the Same

Aug 19, 2026

Why Everything Your AI Builds Looks the Same
This blog explores why AI-generated interfaces often look alike and explains how design systems, product context, and reusable engineering practices help teams build distinctive, scalable
How We Built the Missing Bridge from Code to Figma
Technology

Jul 10, 2026

How We Built the Missing Bridge from Code to Figma
This blog explores how AI-generated React apps get turned into fully editable, designer-ready Figma files by reading React Fiber instead of the DOM.
Building a Resilient Hybrid-Cloud Network with WireGuard HA, Route-Based Failover, and Deep Observability
Technology

Jun 27, 2026

Building a Resilient Hybrid-Cloud Network with WireGuard HA, Route-Based Failover, and Deep Observability
A practical breakdown of building resilient AWS-to-on-premises connectivity with WireGuard HA, active-standby failover, and deep packet-forwarding observability.
We Built a 114-Second AWS-to-Azure Failover. Here’s What We Learned
Technology

Jun 19, 2026

We Built a 114-Second AWS-to-Azure Failover. Here’s What We Learned
A practical guide to building a 114-second multi-cloud disaster recovery failover between AWS and Azure — what we built, what broke, and what we learned.
Cloud-Native and Cloud-Agnostic Are Not Ideologies; They Are Business-Stage Decisions
Technology

Jun 12, 2026

Cloud-Native and Cloud-Agnostic Are Not Ideologies; They Are Business-Stage Decisions
This blog explains how organizations can balance speed, scalability, and operational flexibility as they grow from startup to enterprise scale.

The Right Conversation Can Save You Six Months.

Whether you’re navigating AI adoption, modernizing legacy systems, or scaling a product - we start by listening. No pitch deck. No template. A real conversation.

Apollo Client Network Interceptors in React Native- GeekyAnts