Exchanges
Customize how GraphQL operations are executed with composable middleware.
What are Exchanges?
Exchanges are composable middleware that process your GraphQL operations. Each exchange can modify requests, handle responses, or add features like authentication, caching, and logging.
Acknowledgment
Mearie's exchange architecture is based on urql's exchange system, which provides a proven pattern for composable GraphQL middleware.
Basic Usage
import { createClient, httpExchange, cacheExchange } from '@mearie/react'; // or @mearie/vue, @mearie/svelte, @mearie/solid
import { schema } from '$mearie';
export const client = createClient({
schema,
exchanges: [cacheExchange(), httpExchange({ url: 'https://api.example.com/graphql' })],
});How Exchanges Work
Exchanges use a stream-based architecture. Operations flow as a stream through each exchange, and results flow back:
Operations Stream
↓
[dedupExchange] ← Filters duplicate operations
↓
[cacheExchange] ← Returns cached data or forwards
↓
[httpExchange] ← Executes network requests
↓
Results StreamEach exchange receives a stream of operations, transforms it, and produces a stream of results. This enables:
- Reactive - Operations automatically flow through the pipeline
- Composable - Exchanges operate independently and compose cleanly
- Cancellable - Unsubscribing cancels in-flight operations
Learn More
For a conceptual understanding of streams, see Streams Concept. For the complete API, see Streams Reference.
Terminating vs Non-terminating Exchanges
Exchanges fall into two categories based on their role in the stream pipeline:
Non-terminating exchanges transform the operation stream and forward it:
cacheExchange- May emit cached results or forward operationsretryExchange- Retries operations by replaying them in the streamdedupExchange- Filters duplicate operations from the stream
Terminating exchanges handle operations and don't forward them further:
httpExchange- Executes queries/mutations over HTTPsubscriptionExchange- Handles subscriptions via SSE or WebSocket
WARNING
You must include at least one terminating exchange (usually httpExchange) at the end of your chain. Without it, your operations won't execute.
Execution Order
The order you define exchanges determines the execution flow:
export const client = createClient({
schema,
exchanges: [
dedupExchange(), // 1. Deduplication (outermost)
retryExchange(), // 2. Retry logic
cacheExchange(), // 3. Cache layer
httpExchange(), // 4. Network request (innermost)
],
});This means:
- Deduplication filters duplicate operations first
- Retries will trigger the cache + HTTP flow (downstream only)
- Cache can return early without hitting HTTP
- HTTP terminates the chain by making the actual request
Built-in Exchanges
HTTP Exchange
Sends GraphQL requests over HTTP (terminating exchange):
import { schema } from '$mearie';
export const client = createClient({
schema,
exchanges: [httpExchange({ url: 'https://api.example.com/graphql' })],
});Learn more about HTTP Exchange →
Cache Exchange
Normalized caching with automatic updates:
import { schema } from '$mearie';
export const client = createClient({
schema,
exchanges: [cacheExchange(), httpExchange({ url: 'https://api.example.com/graphql' })],
});Learn more about Cache Exchange →
Retry Exchange
Automatic retry with exponential backoff:
import { schema } from '$mearie';
export const client = createClient({
schema,
exchanges: [retryExchange({ maxAttempts: 3 }), httpExchange({ url: 'https://api.example.com/graphql' })],
});Learn more about Retry Exchange →
Deduplication Exchange
Deduplicates identical concurrent requests:
import { schema } from '$mearie';
export const client = createClient({
schema,
exchanges: [dedupExchange(), cacheExchange(), httpExchange({ url: 'https://api.example.com/graphql' })],
});Learn more about Deduplication Exchange →
Subscription Exchange
Handles real-time subscriptions via Server-Sent Events or WebSocket (terminating exchange):
import { createClient as createSSEClient } from 'graphql-sse';
import { schema } from '$mearie';
export const client = createClient({
schema,
exchanges: [
httpExchange({ url: 'https://api.example.com/graphql' }),
subscriptionExchange({
client: createSSEClient({
url: 'https://api.example.com/graphql',
}),
}),
],
});Learn more about Subscription Exchange →
Common Patterns
Full-featured Client
import { createClient as createSSEClient } from 'graphql-sse';
import { schema } from '$mearie';
export const client = createClient({
schema,
exchanges: [
dedupExchange(),
retryExchange({ maxAttempts: 3 }),
cacheExchange(),
httpExchange({ url: 'https://api.example.com/graphql' }),
subscriptionExchange({
client: createSSEClient({
url: 'https://api.example.com/graphql',
}),
}),
],
});Retry on Network Errors
import { schema } from '$mearie';
export const client = createClient({
schema,
exchanges: [
retryExchange({
maxAttempts: 3,
retryIf: (error) => error.networkError,
}),
httpExchange({ url: 'https://api.example.com/graphql' }),
],
});Creating Custom Exchanges
Exchanges transform operation streams. Here's a simple logging exchange:
import { pipe, tap } from '@mearie/core/stream';
import { type Exchange } from '@mearie/react';
import { schema } from '$mearie';
const loggingExchange = (): Exchange => {
return ({ forward }) => {
return (ops$) => {
return pipe(
ops$,
tap((op) => console.log('Operation:', op)),
forward,
tap((result) => console.log('Result:', result)),
);
};
};
};
export const client = createClient({
schema,
exchanges: [loggingExchange(), httpExchange({ url: 'https://api.example.com/graphql' })],
});The exchange:
- Receives operations stream
ops$ - Logs each operation with
tap - Forwards to next exchange with
forward - Logs each result with
tap - Returns the transformed results stream
Learn more about Custom Exchanges →
Exchange Interface
type Exchange = (input: ExchangeInput) => ExchangeIO;
type ExchangeInput = {
forward: ExchangeIO;
client: Client;
};
type ExchangeIO = (operations: Source<Operation>) => Source<OperationResult>;An exchange:
- Takes
forward(next exchange) andclientas input - Returns a function that transforms operation streams to result streams
- Uses stream operators to transform operations and results
Best Practices
- Order exchanges from outermost (retry, dedup) to innermost (HTTP)
- Always end the chain with a terminating exchange (e.g.,
httpExchange) - Keep custom exchanges simple and focused on a single concern
Next Steps
- Streams Concept - Understand the stream architecture
- Custom Exchanges - Build your own exchanges
- HTTP Exchange - Execute GraphQL over HTTP
- Cache Exchange - Add normalized caching
- Streams Reference - Complete stream API documentation