iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…