Learn GraphQL - URQL, Relay, and React Query
Episode 26 of 51

Learn GraphQL - URQL, Relay, and React Query

Episode 26 compares alternative GraphQL clients: URQL with its exchanges system, Relay with its compiler and fragment colocation, the React Query and graphql-request combination, and a guide to choosing based on bundle size, learning curve, and application needs.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Apollo Client isn't the only option. Episode 26 covers three popular alternatives — URQL, Relay, and React Query — so you can pick the right client for each project's needs.

We'll dissect URQL's philosophy and setup with its exchanges system, Relay's compiler and fragment colocation approach, the React Query and graphql-request combination, then close with a thorough comparison.

URQL

Philosophy and Setup

URQL is built on the philosophy of "as small as possible, but extensible". Its bundle size is small, and almost any behavior can be modified via exchanges — structured middleware that processes every operation. Install with npm install urql graphql:

JSURQL client setup
import { Client, cacheExchange, fetchExchange, Provider } from "urql";
 
const client = new Client({
  url: "http://localhost:4000",
  exchanges: [cacheExchange, fetchExchange],
});
 
function App() {
  return <Provider value={client}>{/* komponen */}</Provider>;
}

Exchanges and Hooks

Each exchange handles one responsibility: cacheExchange for caching, fetchExchange for networking, dedupExchange for deduplication. You can arrange the order yourself, even write a custom exchange:

JSQuery with the URQL hook
import { useQuery, gql } from "urql";
 
const GET_USER = gql`
  query GetUser($id: ID!) {
    user(id: $id) { id username }
  }
`;
 
function Profile({ id }) {
  const [result] = useQuery({ query: GET_USER, variables: { id } });
  if (result.fetching) return <p>Memuat...</p>;
  return <p>{result.data?.user.username}</p>;
}

URQL offers two caches: the document cache (simple, good for small UIs) and the normalized cache (like Apollo, with type policies). Choose according to your data's complexity.

Relay

Compiler and Fragment Colocation

Relay is the most disciplined client. Every component declares its own fragment right next to the data it uses — this is called fragment colocation — then the Relay compiler turns queries into optimized artifacts:

Install Relay
npm install relay-runtime react-relay
npm install -D relay-compiler
Fragment colocation
fragment UserCard_user on User {
  id
  username
  avatar
}
JSComponent with a fragment
import { graphql, useFragment } from "react-relay";
 
function UserCard({ userRef }) {
  const user = useFragment(
    graphql`
      fragment UserCard_user on User {
        id
        username
      }
    `,
    userRef
  );
  return <div>{user.username}</div>;
}

When to Use Relay

Relay excels at large applications with highly connected data — complex feeds, large-scale React Native — because the compiler guarantees queries are always consistent and Relay spec pagination (episode 12) is very mature. The trade-offs: a high learning curve, and server cooperation (must use the Relay spec) makes Relay less flexible for generic APIs.

React Query + GraphQL

graphql-request and Custom Hooks

The minimalist approach: use React Query for server state and graphql-request as a thin GraphQL client:

Install the lightweight combination
npm install @tanstack/react-query graphql-request
JSCustom hook with React Query
import { useQuery } from "@tanstack/react-query";
import { GraphQLClient, gql } from "graphql-request";
 
const client = new GraphQLClient("http://localhost:4000");
 
function useUser(id) {
  return useQuery({
    queryKey: ["user", id],
    queryFn: () =>
      client.request(
        gql`
          query GetUser($id: ID!) {
            user(id: $id) { id username }
          }
        `,
        { id }
      ),
  });
}

React Query handles caching, retries, and stale time in a transport-agnostic way — not tied to the GraphQL ecosystem. Its strengths: flexible, and easy to use with REST too. Its weaknesses: no cache normalization, so duplicated data across queries isn't synchronized automatically.

Comparison and Selection Guide

Apollo vs URQL vs Relay

AspectApolloURQLRelay
Bundle sizeLargeSmallMedium
Learning curveMediumLowHigh
Normalized cacheYesOptionalYes
CompilerNoNoYes (required)
Apollo ecosystemDeeply integratedIndependentIndependent

Usage Recommendations

  • Apollo Client: the best default for most applications — a complete ecosystem, extensive documentation, and seamless integration with Apollo tooling.
  • URQL: for apps that need a small bundle or full control over behavior via exchanges.
  • Relay: for large apps with highly connected data and a team willing to follow its compiler discipline.
  • React Query + graphql-request: for projects using both GraphQL and REST, or teams already familiar with React Query.

There's no absolutely wrong choice — the important thing is to match your team size, data complexity, and bundle size needs.

Conclusion

Key takeaways:

  • URQL uses exchanges that can be ordered and written custom.
  • Relay uses a compiler and fragment colocation for guaranteed-consistent queries.
  • React Query plus graphql-request is a lightweight, flexible cross-transport combination.
  • Apollo suits most apps; URQL for small bundles; Relay for large connected apps.
  • The client choice affects bundle size, learning curve, and how caching works.

In the next episode, episode 27, you'll learn about integrating GraphQL with Next.js — API routes for Apollo Server, SSR with getServerSideProps and hydration, SSG with getStaticProps and ISR, App Router with Server Components, authentication with cookies, and deployment to Vercel. You'll master full-stack GraphQL with Next.js!

Learn GraphQL - URQL, Relay, and React Query | Learn GraphQL