URQL
urql is the lightweight GraphQL client for React, Vue, Svelte, and vanilla JS. It is smaller than Apollo, ships with sensible defaults, and uses an exchanges pipeline (think middleware) so caching, auth, and persistence are composable. Pick it when you want Apollos features without the bundle weight.
Set up urql with auth + cache exchange
EXAMPLE
// npm i urql @urql/exchange-auth @urql/exchange-graphcache graphql
import { Client, Provider, cacheExchange, fetchExchange, gql, useQuery, useMutation, useSubscription } from 'urql';
import { authExchange } from '@urql/exchange-auth';
import { cacheExchange as normalisedCacheExchange } from '@urql/exchange-graphcache';
// 1) Auth exchange — fetches + refreshes tokens
const auth = authExchange(async (utilities) => {
let token = localStorage.getItem('access');
return {
addAuthToOperation(op) {
return token ? utilities.appendHeaders(op, { authorization: \`Bearer ${token}\` }) : op;
},
didAuthError(error) {
return error.graphQLErrors.some((e) => e.extensions?.code === 'UNAUTHENTICATED');
},
async refreshAuth() {
const res = await fetch('/refresh', { method: 'POST', credentials: 'include' });
if (!res.ok) { token = null; localStorage.removeItem('access'); return; }
const json = await res.json();
token = json.accessToken;
localStorage.setItem('access', token);
},
willAuthError(op) {
return !token && op.kind !== 'mutation'; // skip refresh on signup/login
},
};
});
// 2) Normalised cache (Apollo-style) with manual updates after mutations
const cache = normalisedCacheExchange({
keys: { Order: (data: any) => data.id },
updates: {
Mutation: {
cancelOrder(result: any, _args, cache) {
cache.invalidate({ __typename: 'Order', id: result.cancelOrder.id });
},
},
},
optimistic: {
cancelOrder: (variables: any) => ({
__typename: 'CancelOrderPayload',
cancelOrder: { __typename: 'Order', id: variables.id, status: 'cancelled' },
}),
},
});
// 3) Build the client — exchanges run left to right
export const client = new Client({
url: '/api/graphql',
exchanges: [cache, auth, fetchExchange], // cacheExchange (basic) OR normalised one
fetchOptions: () => ({ credentials: 'include' }),
});
// 4) Provide at the root
export default function App({ children }: any) {
return <Provider value={client}>{children}</Provider>;
}
// 5) Queries — hook + paginated cursor
const ORDERS = gql\`
query Orders($status: OrderStatus, $after: String) {
orders(status: $status, after: $after) {
data { id customer totalCents status }
nextCursor
}
}
\`;
function OrdersList({ status }: { status?: string }) {
const [result, refetch] = useQuery({ query: ORDERS, variables: { status } });
const { data, fetching, error } = result;
if (fetching && !data) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>{data!.orders.data.map((o: any) => <li key={o.id}>{o.customer}</li>)}</ul>
);
}
// 6) Mutations + optimistic UI
const CANCEL = gql\`
mutation Cancel($id: ID!) { cancelOrder(id: $id) { id status } }
\`;
function CancelButton({ id }: { id: string }) {
const [_, cancel] = useMutation(CANCEL);
return <button onClick={() => cancel({ id })}>Cancel</button>;
}
// 7) Subscriptions
const TICKER = gql\`subscription { ticker { id price } }\`;
function PriceTicker() {
const [{ data }] = useSubscription({ query: TICKER });
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
// 8) Persisting + offline
// import { persistedExchange } from '@urql/exchange-persisted';
// Add 'persistedExchange()' to the exchanges array; it transparently
// converts queries to APQ (Automatic Persisted Queries) hashes.
// 9) Why pick urql over Apollo
// - Bundle: ~7 kB gzipped (default exchanges); Apollo is ~30-40 kB
// - Architecture: exchanges are easier to reason about than Apollo links
// - Adapters for React, Vue, Svelte, Preact, Next.js
// - Easier to mock for tests (TestProvider with a mockClient)
Why it matters
urqls exchanges architecture is its superpower: each exchange is a tiny middleware that handles one concern (auth, caching, retry, logging). Composing them feels like building a Linux pipeline rather than configuring a monolith. Reach for it when the Apollo footprint or its API shape feels heavy for the feature you are building.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import { createClient, fetchExchange } from 'urql';
const client = createClient({ url: '/graphql', exchanges: [fetchExchange] });
Try it Yourself »
Discussion
Loading…