@octokit/graphql, apollo-client, graphql-request, graphql-tag, and urql are all JavaScript packages that help frontend applications interact with GraphQL APIs, but they serve different scopes and use cases. @octokit/graphql is a specialized client for GitHub’s GraphQL API. apollo-client and urql are full-featured GraphQL clients that handle caching, state management, and real-time updates. graphql-request is a minimal, promise-based HTTP client for making GraphQL queries without extra features. graphql-tag is not a client at all — it’s a utility for parsing GraphQL query strings into AST documents, typically used alongside Apollo or other clients.
Frontend developers today have several options for talking to GraphQL APIs. The five packages — @octokit/graphql, apollo-client, graphql-request, graphql-tag, and urql — might seem similar at first glance, but they solve very different problems. Let’s cut through the confusion with real code and clear trade-offs.
It’s critical to understand that not all of these are full GraphQL clients.
@octokit/graphql: A thin wrapper around fetch tuned only for GitHub’s GraphQL API.apollo-client: A complete state management and data fetching system built around GraphQL.graphql-request: A minimal HTTP client that sends POST requests to any GraphQL endpoint.graphql-tag: A parser that turns GraphQL query strings into AST objects — no networking involved.urql: A reactive, stream-based GraphQL client focused on simplicity and performance.If you try to use graphql-tag to make a network request, it won’t work — it’s just a parser. Similarly, @octokit/graphql won’t help you talk to your company’s internal GraphQL API unless it’s GitHub.
Here’s how each package (that supports it) sends a simple query.
@octokit/graphql (GitHub-only)import { graphql } from "@octokit/graphql";
const { repository } = await graphql(
`
query {
repository(owner: "octokit", name: "graphql.js") {
description
}
}
`,
{
headers: {
authorization: `token ${process.env.GITHUB_TOKEN}`
}
}
);
Note: You must provide a GitHub token. This won’t work with other GraphQL servers.
apollo-clientimport { ApolloClient, InMemoryCache, gql, HttpLink } from "@apollo/client";
const client = new ApolloClient({
link: new HttpLink({ uri: "/graphql" }),
cache: new InMemoryCache()
});
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
name
}
}
`;
const { data } = await client.query({
query: GET_USER,
variables: { id: "123" }
});
Apollo requires setup (cache, link) and uses gql (from graphql-tag) to parse queries.
graphql-requestimport { request, gql } from "graphql-request";
const query = gql`
query GetUser($id: ID!) {
user(id: $id) {
name
}
}
`;
const data = await request("/graphql", query, { id: "123" });
This is the simplest: one function call, no setup. The gql here is a convenience tag that just returns the string — it doesn’t parse to AST.
urqlimport { createClient, gql } from "urql";
const client = createClient({
url: "/graphql"
});
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
name
}
}
`;
const result = await client.query(GET_USER, { id: "123" }).toPromise();
Urql uses a similar gql tag (also from graphql-tag under the hood) and returns an observable, so you call .toPromise() for async/await.
graphql-tag (Not a client!)import gql from "graphql-tag";
const query = gql`
query GetUser($id: ID!) {
user(id: $id) {
name
}
}
`;
// query is now a parsed AST object
// You still need a client (like Apollo or urql) to send it
This does nothing on its own. It’s just a build-time or runtime parser.
This is where the big divide appears.
apollo-client: Offers normalized caching. If two queries return the same User object, Apollo stores it once by ID and keeps all references in sync. Supports cache updates, optimistic responses, and garbage collection.
urql: Provides document caching by default (caches full query results), with optional graph cache (similar to Apollo’s normalization) via @urql/exchange-graphcache. Simpler but less automatic than Apollo.
graphql-request, @octokit/graphql, and graphql-tag: No caching at all. Every request hits the network.
If your app shows the same user profile in multiple places and you update it in one, you’ll need Apollo or urql to keep everything consistent without manual refetching.
graphql-request — one import, one function call.urql — requires client creation but minimal config.apollo-client — needs cache, link, and often context providers in React.@octokit/graphql — trivial setup, but only for GitHub.graphql-tag — just a utility.For a small marketing site that fetches blog posts once, graphql-request is overkill to replace with Apollo. But for a dashboard with live data, Apollo’s complexity pays off.
apollo-client: Full subscription support via WebSocket links.urql: Built-in subscription support using the same stream model.graphql-request, @octokit/graphql, graphql-tag: No subscription support. They’re HTTP-only.If you need live updates (e.g., chat messages, stock prices), stick with Apollo or urql.
All three full clients (apollo-client, urql, and even graphql-request via custom hooks) work with React, but:
useQuery, useMutation, and ApolloProvider.useQuery, useMutation, and Provider.graphql-request requires you to write your own useEffect + useState hook.Example with graphql-request in React:
function UserProfile({ id }) {
const [user, setUser] = useState(null);
useEffect(() => {
request("/graphql", GET_USER, { id }).then(setUser);
}, [id]);
return <div>{user?.name}</div>;
}
Compare that to Apollo:
function UserProfile({ id }) {
const { data } = useQuery(GET_USER, { variables: { id } });
return <div>{data?.user.name}</div>;
}
Apollo gives you loading/error states, caching, and refetching out of the box.
As of the latest official sources:
graphql-tag is stable and unlikely to change — it’s a small, focused utility.It’s common to use graphql-tag alongside apollo-client or urql, because both expect parsed AST documents. In fact, Apollo’s gql is just a re-export of graphql-tag.
You would never combine @octokit/graphql with another GraphQL client — it’s a standalone tool for one specific API.
| Package | Full Client? | Caching | Subscriptions | GitHub Only? | React Hooks |
|---|---|---|---|---|---|
@octokit/graphql | ❌ (HTTP only) | ❌ | ❌ | ✅ | ❌ |
apollo-client | ✅ | ✅ (normalized) | ✅ | ❌ | ✅ |
graphql-request | ❌ (HTTP only) | ❌ | ❌ | ❌ | ❌ (manual) |
graphql-tag | ❌ (parser only) | ❌ | ❌ | ❌ | ❌ |
urql | ✅ | ✅ (document/graph) | ✅ | ❌ | ✅ |
@octokit/graphqlapollo-clientgraphql-requestgraphql-tag (as a helper)urqlDon’t over-engineer: if you don’t need caching or real-time updates, skip the heavy clients. But if your app lives and breathes data, invest in Apollo or urql early — it’s hard to retrofit later.
Choose urql when you want a lightweight, stream-based GraphQL client with good caching and React support but less complexity than Apollo. It’s well-suited for medium-sized apps that need real-time updates (via subscriptions) and cache invalidation without heavy configuration.
Choose @octokit/graphql only when you’re building integrations specifically with GitHub’s GraphQL API. It handles authentication with GitHub tokens and simplifies requests to their endpoint, but it doesn’t support caching, subscriptions, or general-purpose GraphQL usage. Avoid it for non-GitHub APIs.
Choose apollo-client when you need a mature, feature-rich GraphQL client with normalized caching, optimistic UI, local state management, and strong React integration. It’s ideal for large applications where data consistency, offline support, and complex querying patterns matter, though it comes with more bundle size and configuration overhead.
Choose graphql-request when you want the simplest possible way to send GraphQL queries over HTTP without caching, state management, or reactivity. It’s perfect for small apps, scripts, or server-side usage where you just need to fetch data once and move on.
Choose graphql-tag only as a companion tool when working with Apollo Client or similar libraries that expect GraphQL queries as parsed AST documents. It should never be used alone — it doesn’t make network requests. Its main role is enabling syntax highlighting and static analysis in tooling.
A highly customizable and versatile GraphQL client for React
More documentation is available at formidable.com/open-source/urql.