graphql-request vs urql vs apollo-client vs react-apollo
GraphQL Client Libraries for React Applications
graphql-requesturqlapollo-clientreact-apolloSimilar Packages:

GraphQL Client Libraries for React Applications

apollo-client and react-apollo are legacy packages that have been unified into the modern @apollo/client library, providing a comprehensive state management and data fetching solution for GraphQL. graphql-request is a minimal, flexible client focused on simple HTTP requests without built-in caching or React bindings. urql is a highly customizable GraphQL client built by Formidable, offering a balanced approach between features and bundle size with a modular exchange system. Together, these tools represent the spectrum from full-featured frameworks to lightweight utilities for handling GraphQL in JavaScript applications.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
graphql-request13,112,8486,121420 kB60a year agoMIT
urql1,518,1508,985140 kB332 months agoMIT
apollo-client583,95619,781-4106 years agoMIT
react-apollo06,790-2057 years agoMIT

GraphQL Clients for React: Apollo, urql, and graphql-request Compared

Choosing the right GraphQL client impacts how your application handles data fetching, caching, and state management. The landscape includes legacy tools that shaped the ecosystem and modern alternatives designed for simplicity and speed. Let's compare apollo-client, react-apollo, graphql-request, and urql to help you make an informed architectural decision.

⚠️ Critical Status Check: Legacy vs Modern

Before writing code, you must know the maintenance status of these packages. Two of the four options are deprecated and should not be used in new projects.

apollo-client is the legacy core package. It has been replaced by @apollo/client.

// apollo-client (Legacy - DO NOT USE)
import ApolloClient from 'apollo-client';
const client = new ApolloClient({ uri: '/graphql' });

react-apollo is the legacy React binding. It is merged into @apollo/client.

// react-apollo (Legacy - DO NOT USE)
import { useQuery } from 'react-apollo';
const { data } = useQuery(MY_QUERY);

graphql-request is actively maintained and stable for minimal use cases.

// graphql-request (Current)
import { request } from 'graphql-request';
const data = await request(endpoint, query);

urql is actively maintained and recommended for modern React apps.

// urql (Current)
import { createClient } from '@urql/core';
const client = createClient({ url: '/graphql' });

🛠️ Setup and Configuration

Setting up the client determines how much boilerplate you write initially. Apollo requires the most configuration, while graphql-request requires almost none.

apollo-client (Legacy) needed separate setup for the client and React provider.

// apollo-client (Legacy)
import ApolloClient from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
const client = new ApolloClient({ cache: new InMemoryCache(), uri: '/graphql' });

react-apollo (Legacy) wrapped the app in a provider to access hooks.

// react-apollo (Legacy)
import { ApolloProvider } from 'react-apollo';
ReactDOM.render(<ApolloProvider client={client}><App /></ApolloProvider>, root);

graphql-request does not require a provider or complex setup.

// graphql-request
import { GraphQLClient } from 'graphql-request';
const client = new GraphQLClient('/graphql', { headers: { ... } });

urql uses a client instance passed via a provider, similar to modern Apollo.

// urql
import { Provider, createClient } from 'urql';
const client = createClient({ url: '/graphql' });
ReactDOM.render(<Provider value={client}><App /></Provider>, root);

📥 Fetching Data: Hooks vs Functions

How you fetch data in components defines your developer experience. Hooks are standard now, but some libraries still rely on function calls.

apollo-client (Legacy) used hooks from the separate react-apollo package.

// apollo-client + react-apollo (Legacy)
import { useQuery } from 'react-apollo';
function User() {
  const { data } = useQuery(USER_QUERY);
  return <div>{data?.user.name}</div>;
}

react-apollo (Legacy) was the specific package providing these hooks.

// react-apollo (Legacy)
import { useMutation } from 'react-apollo';
const [login] = useMutation(LOGIN_MUTATION);

graphql-request relies on standard fetch patterns inside effects or handlers.

// graphql-request
import { request } from 'graphql-request';
function User() {
  const [data, setData] = useState(null);
  useEffect(() => { request('/graphql', USER_QUERY).then(setData); }, []);
  return <div>{data?.user.name}</div>;
}

urql provides built-in hooks that handle loading and error states automatically.

// urql
import { useQuery } from 'urql';
function User() {
  const [result] = useQuery({ query: USER_QUERY });
  return <div>{result.data?.user.name}</div>;
}

🔄 Caching and State Management

Caching reduces network requests and improves UI speed. This is where the differences become stark.

apollo-client (Legacy) included a powerful normalized cache out of the box.

// apollo-client (Legacy)
import { InMemoryCache } from 'apollo-cache-inmemory';
const cache = new InMemoryCache();
// Automatically normalizes data by ID

react-apollo (Legacy) connected components to this cache.

// react-apollo (Legacy)
// Components re-render automatically when cache data changes

graphql-request has no built-in caching. You must implement it yourself.

// graphql-request
// No cache. Every call hits the network.
const data = await request('/graphql', QUERY);
// You must store 'data' in React state or localStorage manually

urql includes a document cache by default, with optional normalized caching.

// urql
import { cacheExchange } from '@urql/core';
const client = createClient({ exchanges: [cacheExchange, ...] });
// Caches queries by default; add graphcache for normalization

⚠️ Error Handling

Handling failures gracefully is critical for production apps. Each library exposes errors differently.

apollo-client (Legacy) returned errors in the hook result object.

// apollo-client + react-apollo (Legacy)
const { error } = useQuery(USER_QUERY);
if (error) return <p>Failed to load</p>;

react-apollo (Legacy) provided the error boundary context.

// react-apollo (Legacy)
import { ApolloError } from 'apollo-client';
// Manual checking of error types required

graphql-request throws errors that you must catch manually.

// graphql-request
try {
  await request('/graphql', QUERY);
} catch (error) {
  console.error(error.response.errors);
}

urql includes error state directly in the result tuple.

// urql
const [result] = useQuery({ query: USER_QUERY });
if (result.error) return <p>Failed to load</p>;

📊 Summary: Key Differences

Featureapollo-client / react-apollographql-requesturql
Status❌ Deprecated (Use @apollo/client)✅ Active✅ Active
Bundle Size🐘 Large🐦 Tiny🐥 Small
Caching🔄 Normalized (Built-in)❌ None🔄 Document (Built-in)
React Hooks✅ Yes (Legacy)❌ No (Manual)✅ Yes
Complexity📈 High📉 Low📊 Medium

💡 The Big Picture

apollo-client and react-apollo are the foundation of the old GraphQL ecosystem. They are powerful but now obsolete. If you need Apollo's features, use @apollo/client. It is best for enterprise apps needing complex state sync.

graphql-request is the lightweight choice. It works well for scripts, server-side code, or simple apps where you do not need caching. You trade convenience for control and size.

urql is the modern middle ground. It offers hooks and caching without the heaviness of Apollo. It is ideal for teams wanting a clean API and room to customize via exchanges.

Final Thought: Avoid apollo-client and react-apollo in new work. Choose urql for balanced React integration or graphql-request for minimal needs. If you require the full Apollo ecosystem, migrate to @apollo/client immediately.

How to Choose: graphql-request vs urql vs apollo-client vs react-apollo

  • graphql-request:

    Choose graphql-request if you need a lightweight, framework-agnostic solution for simple queries without the overhead of a full client. It is ideal for server-side scripts, Next.js API routes, or React apps where you prefer to manage caching and state manually or do not need advanced features like optimistic updates.

  • urql:

    Choose urql if you want a modern, modular client that balances features with performance. It is suitable for teams that need customization via exchanges without the complexity of Apollo, offering strong TypeScript support and a simpler API for standard data fetching and caching needs.

  • apollo-client:

    Avoid using the legacy apollo-client package in new projects as it is deprecated. Instead, choose the modern @apollo/client which unifies the core client and React bindings. This path is best for large-scale applications requiring robust caching, optimistic UI updates, and complex state management alongside GraphQL data.

  • react-apollo:

    Do not use react-apollo for new development as it is officially deprecated and no longer maintained. Its features have been merged into @apollo/client. Selecting this package introduces security risks and lack of support, so migrate to the unified Apollo client or consider alternatives like urql.

README for graphql-request

graphql-request

Minimal GraphQL client supporting Node and browsers for scripts or simple apps.

GitHub Action npm version

Highlights

  • Most simple & lightweight GraphQL client
  • Promise-based API (works with async / await)
  • Pure ESM package
  • First class TypeScript support
    • Including TypedDocumentNode
  • Isomorphic (works in both Node and Browsers)

Install

npm add graphql-request graphql

TypeScript Setup

This package uses package.exports. Therefore if you are a TypeScript user you must:

  1. have your tsconfig.json moduleResolution set to "bundler" or "node16"/"nodenext".
  2. Have your package.json type set to "module".

Quick Start

Send a GraphQL document using a static request function:

import { gql, request } from 'graphql-request'

const document = gql`
  {
    company {
      ceo
    }
  }
`
await request('https://api.spacex.land/graphql/', document)

The function can be passed a configuration object for more complex cases:

await request({
  url,
  document,
  variables,
  requestHeaders,
})

A class is available for constructing your own instances:

import { gql, GraphQLClient } from 'graphql-request'

const document = gql`
  {
    company {
      ceo
    }
  }
`
const endpoint = 'https://api.spacex.land/graphql/'
const client = new GraphQLClient(endpoint)
await client.request(document)

Examples

Node Version Support

We only (officially) support versions of Nodejs of the following status:

  • Current
  • LTS
  • Maintenance and end of life not yet reached

So for example on Oct 24 2023 that would mean these versions: 18, 20, 21.

Any issue that exists solely for an unsupported version of Nodejs will be rejected (not worked on).

Reference

⚠️ This reference is incomplete. Check out the examples for more reference material.

Configuration

ErrorPolicy

By default GraphQLClient will throw when an error is received. However, sometimes you still want to resolve the (partial) data you received. You can define errorPolicy in the GraphQLClient constructor.

const client = new GraphQLClient(endpoint, { errorPolicy: 'all' })
None (default)

Allow no errors at all. If you receive a GraphQL error the client will throw.

Ignore

Ignore incoming errors and resolve like no errors occurred

All

Return both the errors and data, only works with rawRequest.

IgnoreOperationName

OperationName has been introduced to address issues reported here Support operation name, However, on certain occasions this information may not be needed in requests. In such cases, you might consider ignoring operationName to avoid the extraction steps currently performed by a parsing operation when the document is provided in string format.

By default the GraphQLClient tries to extract the operationName from the document. You can define excludeOperationName in the constructor of GraphQLClient to avoid the extraction process if it is not needed. This can be useful if you don't use operationName and want to optimise queries by reducing the amount of computation as much as possible, especially if we are in a context where we are using documents in string format to reduce bundle size.

// example where the operation name is not ignored
const client = new GraphQLClient(endpoint, {
  method: 'POST',
})
// example in which the operation name is ignored
const client = new GraphQLClient(endpoint, {
  method: 'POST',
  excludeOperationName: true,
})

Knowledge Base

Why was the file upload feature taken away? Will it return?

In this issue we decided to make this library more stable and maintainable. In principal the feature is still in scope of this library and will make a return when we find time to do the feature right.

Why do I have to install graphql?

graphql-request uses methods exposed by the graphql package to handle some internal logic. On top of that, for TypeScript users, some types are used from the graphql package to provide better typings.

Do I need to wrap my GraphQL documents inside the gql template exported by graphql-request?

No. It is there for convenience so that you can get the tooling support like automatic formatting and syntax highlighting. You can use gql from graphql-tag if you need it for some reason too.

What sets graphql-request apart from other clients like Apollo, Relay, etc.?

graphql-request is the most minimal and simplest to use GraphQL client. It's perfect for small scripts or simple apps.

Compared to GraphQL clients like Apollo or Relay, graphql-request doesn't have a built-in cache and has no integrations for frontend frameworks. The goal is to keep the package and API as minimal as possible.

Project Stats

Package Installs

NPM Usage Trend of graphql-request

Repo Beats

Alt