apollo-client, graphql-request, and urql are tools for fetching GraphQL data in JavaScript applications. apollo-client was the industry standard but is now deprecated in favor of @apollo/client. graphql-request is a minimal function-based client ideal for scripts or simple needs. urql is a modular client focused on simplicity and extensibility without heavy configuration. Each serves different architectural needs regarding caching, state management, and setup complexity.
These three libraries solve the same problem β fetching GraphQL data β but they approach it differently. apollo-client (now @apollo/client) is the heavy-duty option. graphql-request is the minimalist tool. urql sits in the middle with a focus on modularity. Let's compare how they handle real-world tasks.
apollo-client is deprecated.
The original apollo-client package is no longer maintained. The team moved to @apollo/client starting with version 3. You should not install apollo-client in new projects.
// apollo-client: Deprecated
// Do not use: import { ApolloClient } from 'apollo-client';
// Modern equivalent: @apollo/client
import { ApolloClient } from '@apollo/client';
graphql-request is stable and minimal.
It has no major framework dependencies. It works in Node.js, browsers, and serverless functions without extra setup.
// graphql-request: Stable
import { request } from 'graphql-request';
const data = await request(endpoint, query, variables);
urql is actively maintained.
It is built by Formidable and focuses on a customizable exchange system. It receives regular updates for React and other frameworks.
// urql: Active
import { createClient } from '@urql/core';
const client = createClient({ url: endpoint });
apollo-client has a normalized cache built-in.
It stores data by ID automatically. This prevents duplicate data and updates UI across components when data changes.
// apollo-client: Normalized cache
import { InMemoryCache } from '@apollo/client';
const client = new ApolloClient({
cache: new InMemoryCache()
});
graphql-request has no cache.
You fetch data every time. You must manage storage yourself using React state or external tools like SWR or React Query.
// graphql-request: No cache
import { request } from 'graphql-request';
// Manual caching required
const data = await request(endpoint, query);
urql has a configurable cache.
It includes a cache but lets you swap it out. The default cache handles normalized data similar to Apollo but with less setup.
// urql: Configurable cache
import { createClient, cacheExchange } from '@urql/core';
const client = createClient({
url: endpoint,
exchanges: [cacheExchange]
});
apollo-client requires a provider wrapper.
You must wrap your app with ApolloProvider. It needs significant boilerplate to set up links and cache.
// apollo-client: Provider setup
import { ApolloProvider } from '@apollo/client';
function App() {
return (
<ApolloProvider client={client}>
<Home />
</ApolloProvider>
);
}
graphql-request needs no provider.
You call functions directly. There is no context or global state required. This makes it easy to test and use outside React.
// graphql-request: No provider
async function loadData() {
const data = await request(endpoint, query);
return data;
}
urql requires a provider but is lighter.
You wrap your app with Provider from urql. The client setup is simpler than Apollo with fewer required options.
// urql: Lightweight provider
import { Provider } from 'urql';
function App() {
return (
<Provider value={client}>
<Home />
</Provider>
);
}
apollo-client uses dedicated hooks.
It provides useMutation with built-in loading and error states. It handles optimistic updates easily.
// apollo-client: useMutation
import { useMutation } from '@apollo/client';
const [addTodo] = useMutation(ADD_TODO);
function Add() {
return <button onClick={() => addTodo()} />;
}
graphql-request treats mutations like queries.
You send a mutation string using the same request function. You handle loading and errors manually with try/catch.
// graphql-request: Manual handling
try {
await request(endpoint, mutation, variables);
} catch (error) {
console.error(error);
}
urql uses a dedicated hook for mutations.
It provides useMutation similar to Apollo. It returns a tuple with the execute function and result state.
// urql: useMutation
import { useMutation } from 'urql';
const [result, executeMutation] = useMutation(ADD_TODO);
function Add() {
return <button onClick={() => executeMutation()} />;
}
apollo-client uses Links.
You chain link objects to handle auth, logging, or batching. It is powerful but can be complex to configure.
// apollo-client: Link chain
import { HttpLink } from '@apollo/client';
const link = new HttpLink({ uri: endpoint });
graphql-request uses interceptors.
You can add headers or modify requests before sending. It is simple but less flexible than a full middleware chain.
// graphql-request: Interceptors
const client = new GraphQLClient(endpoint, {
requestMiddleware: (request) => {
request.headers['Authorization'] = 'token';
return request;
}
});
urql uses Exchanges.
You build a pipeline of functions that process operations. It is very flexible and easier to debug than Apollo Links.
// urql: Exchange pipeline
import { fetchExchange } from '@urql/core';
const client = createClient({
exchanges: [cacheExchange, fetchExchange]
});
| Feature | apollo-client | graphql-request | urql |
|---|---|---|---|
| Status | β οΈ Deprecated (Use @apollo/client) | β Stable | β Active |
| Caching | π¦ Normalized (Auto) | β None | π¦ Configurable |
| Setup | ποΈ Heavy (Provider + Config) | πͺΆ None (Function) | ποΈ Light (Provider) |
| Mutations | π useMutation Hook | π Manual Function | π useMutation Hook |
| Extensions | π Links | π‘οΈ Interceptors | π§© Exchanges |
apollo-client should not be used directly.
If you need the Apollo feature set, install @apollo/client instead. It is best for large enterprise apps where caching and state consistency are critical.
graphql-request is best for simple tasks.
Use it for server-side scripts, static sites, or when you already use React Query for caching. It keeps your app lean.
urql is best for balanced flexibility.
It offers a great developer experience with less complexity than Apollo. Choose it for modern frontend apps that need caching without the heavy boilerplate.
Choose graphql-request if you need a lightweight solution without built-in caching. It works well for server-side scripts, simple static sites, or when you want to manage state manually. It avoids heavy dependencies and keeps your bundle small.
Choose urql if you want a balance between features and simplicity. It offers caching and React hooks like Apollo but with a modular exchange system. It is suitable for teams who want flexibility without the complexity of Apollo's configuration.
Do not choose apollo-client for new projects. It is officially deprecated and moved to @apollo/client. Use the new package if you need a robust cache, strong typing, and a large ecosystem. It is best for complex apps requiring normalized data storage and optimistic UI updates.
Minimal GraphQL client supporting Node and browsers for scripts or simple apps.
async / await)TypedDocumentNodenpm add graphql-request graphql
This package uses package.exports. Therefore if you are a TypeScript user you must:
tsconfig.json moduleResolution set to "bundler" or "node16"/"nodenext".package.json type set to "module".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)
We only (officially) support versions of Nodejs of the following status:
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).
β οΈ This reference is incomplete. Check out the examples for more reference material.
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' })
Allow no errors at all. If you receive a GraphQL error the client will throw.
Ignore incoming errors and resolve like no errors occurred
Return both the errors and data, only works with rawRequest.
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,
})
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.
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.
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.
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.