Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
1--- 2title: Authoring Exchanges 3order: 8 4--- 5 6# Exchange Author Guide 7 8As we've learned [on the "Architecture" page](../architecture.md) page, `urql`'s `Client` structures 9its data as an event hub. We have an input stream of operations, which are instructions for the 10`Client` to provide a result. These results then come from an output stream of operation results. 11 12_Exchanges_ are responsible for performing the important transform from the operations (input) stream 13to the results stream. Exchanges are handler functions that deal with these input and 14output streams. They're one of `urql`'s key components, and are needed to implement vital pieces of 15logic such as caching, fetching, deduplicating requests, and more. In other words, Exchanges are 16handlers that fulfill our GraphQL requests and can change the stream of operations or results. 17 18In this guide we'll learn more about how exchanges work and how we can write our own exchanges. 19 20## An Exchange Signature 21 22Exchanges are akin to [middleware in 23Redux](https://redux.js.org/advanced/middleware) due to the way that they apply transforms. 24 25```ts 26import { Client, Operation, OperationResult } from '@urql/core'; 27 28type ExchangeInput = { forward: ExchangeIO; client: Client }; 29type Exchange = (input: ExchangeInput) => ExchangeIO; 30type ExchangeIO = (ops$: Source<Operation>) => Source<OperationResult>; 31``` 32 33The first parameter to an exchange is a `forward` function that refers to the next Exchange in the 34chain. The second second parameter is the `Client` being used. Exchanges always return an `ExchangeIO` 35function (this applies to the `forward` function as well), which accepts the source of 36[_Operations_](../api/core.md#operation) and returns a source of [_Operation 37Results_](../api/core.md#operationresult). 38 39- [Read more about streams on the "Architecture" page.](../architecture.md#stream-patterns-in-urql) 40- [Read more about the _Exchange_ type signature on the API docs.](../api/core.md#exchange) 41 42## Using Exchanges 43 44The `Client` accepts an `exchanges` option that. Initially, we may choose to just 45set this to two very standard exchanges — `cacheExchange` and `fetchExchange`. 46 47In essence these exchanges build a pipeline that runs in the order they're passed; _Operations_ flow 48in from the start to the end, and _Results_ are returned through the chain in reverse. 49 50Suppose we pass the `cacheExchange` and then the `fetchExchange` to the `exchanges`. 51 52**First,** operations are checked against the cache. Depending on the `requestPolicy`, 53cached results can be resolved from here instead, which would mean that the cache sends back the 54result, and the operation doesn't travel any further in the chain. 55 56**Second,** operations are sent to the API, and the result is turned into an `OperationResult`. 57 58**Lastly,** operation results then travel through the exchanges in _reverse order_, which is because 59exchanges are a pipeline where all operations travel forward deeper into the exchange chain, and 60then backwards. When these results pass through the cache then the `cacheExchange` stores the 61result. 62 63```js 64import { Client, fetchExchange, cacheExchange } from 'urql'; 65 66const client = new Client({ 67 url: 'http://localhost:3000/graphql', 68 exchanges: [cacheExchange, fetchExchange], 69}); 70``` 71 72We can add more exchanges to this chain, for instance, we can add the `mapExchange`, which can call a 73callback whenever it sees [a `CombinedError`](../basics/errors.md) occur on a result. 74 75```js 76import { Client, fetchExchange, cacheExchange, mapExchange } from 'urql'; 77 78const client = new Client({ 79 url: 'http://localhost:3000/graphql', 80 exchanges: [ 81 cacheExchange, 82 mapExchange({ 83 onError(error) { 84 console.error(error); 85 }, 86 }), 87 fetchExchange, 88 ], 89}); 90``` 91 92This is an example for adding a synchronous exchange to the chain that only reacts to results. It 93doesn't add any special behavior for operations travelling through it. An example for an 94asynchronous exchange that looks at both operations and results [we may look at the `retryExchange` 95which retries failed operations.](../advanced/retry-operations.md) 96 97## The Rules of Exchanges 98 99Before we can start writing some exchanges, there are a couple of consistent patterns and limitations that 100must be adhered to when writing an exchange. We call these the "rules of Exchanges", which 101also come in useful when trying to learn what Exchanges actually are. 102 103For reference, this is a basic template for an exchange: 104 105```js 106const noopExchange = ({ client, forward }) => { 107 return operations$ => { 108 // <-- The ExchangeIO function 109 const operationResult$ = forward(operations$); 110 return operationResult$; 111 }; 112}; 113``` 114 115This exchange does nothing else than forward all operations and return all results. Hence, it's 116called a `noopExchange` — an exchange that doesn't do anything. 117 118### Forward and Return Composition 119 120When you create a `Client` and pass it an array of exchanges, `urql` composes them left-to-right. 121If we look at our previous `noopExchange` example in context, we can track what it does if it is located between the `cacheExchange` and the `fetchExchange`. 122 123```js 124import { Client, cacheExchange, fetchExchange } from 'urql'; 125 126const noopExchange = ({ client, forward }) => { 127 return operations$ => { 128 // <-- The ExchangeIO function 129 // We receive a stream of Operations from `cacheExchange` which 130 // we can modify before... 131 const forwardOperations$ = operations$; 132 133 // ...calling `forward` with the modified stream. The `forward` 134 // function is the next exchange's `ExchangeIO` function, in this 135 // case `fetchExchange`. 136 const operationResult$ = forward(operations$); 137 138 // We get back `fetchExchange`'s stream of results, which we can 139 // also change before returning, which is what `cacheExchange` 140 // will receive when calling `forward`. 141 return operationResult$; 142 }; 143}; 144 145const client = new Client({ 146 exchanges: [cacheExchange, noopExchange, fetchExchange], 147}); 148``` 149 150### How to Avoid Accidentally Dropping Operations 151 152Typically the `operations$` stream will send you `query`, `mutation`, 153`subscription`, and `teardown`. There is no constraint for new operations 154to be added later on or a custom exchange adding new operations altogether. 155 156This means that you have to take "unknown" operations into account and 157not `filter` operations too aggressively. 158 159```js 160import { pipe, filter, merge } from 'wonka'; 161 162// DON'T: drop unknown operations 163({ forward }) => 164 operations$ => { 165 // This doesn't handle operations that aren't queries 166 const queries = pipe( 167 operations$, 168 filter(op => op.kind === 'query') 169 ); 170 return forward(queries); 171 }; 172 173// DO: forward operations that you don't handle 174({ forward }) => 175 operations$ => { 176 const queries = pipe( 177 operations$, 178 filter(op => op.kind === 'query') 179 ); 180 const rest = pipe( 181 operations$, 182 filter(op => op.kind !== 'query') 183 ); 184 return forward(merge([queries, rest])); 185 }; 186``` 187 188If operations are grouped and/or filtered by what the exchange is handling, then it's also important to 189make that any streams of operations not handled by the exchange should also be forwarded. 190 191### Synchronous first, Asynchronous last 192 193By default exchanges and Wonka streams are as predictable as possible. 194Every operator in Wonka runs synchronously until asynchronicity is introduced. 195 196This may happen when using a timing utility from Wonka, like 197[`delay`](https://wonka.kitten.sh/api/operators#delay) or 198[`throttle`](https://wonka.kitten.sh/api/operators#throttle) 199This can also happen because the exchange inherently does something asynchronous, like fetching some 200data or using a promise. 201 202When writing exchanges, some will inevitably be asynchronous. For example if 203they're fetching results, performing authentication, or other tasks 204that you have to wait for. 205 206This can cause problems, because the behavior in `urql` is built 207to be _synchronous_ first. This is very helpful for suspense mode and allowing components receive cached data on their initial 208mount without rerendering. 209 210This why **all exchanges should be ordered synchronous first and 211asynchronous last**. 212 213What we for instance repeat as the default setup in our docs is this: 214 215```js 216import { Client, cacheExchange, fetchExchange } from 'urql'; 217 218new Client({ 219 // ... 220 exchanges: [cacheExchange, fetchExchange]; 221}); 222``` 223 224The `cacheExchange` is completely synchronous. 225The `fetchExchange` is asynchronous since it makes a `fetch` request and waits for a server response. 226If we put an asynchronous exchange in front of the `cacheExchange`, that would be unexpected, and 227since all results would then be delayed, nothing would ever be "cached" and instead always take some 228amount of time to be returned. 229 230When you're adding more exchanges, it's often crucial 231to put them in a specific order. For instance, an authentication exchange 232will need to go before the `fetchExchange`, a secondary cache will probably have to 233go in front of the default cache exchange. 234 235To ensure the correct behavior of suspense mode and 236the initialization of our hooks, it's vital to order exchanges 237so that synchronous ones come before asynchronous ones.