Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
1--- 2title: Architecture 3order: 3 4--- 5 6# Architecture 7 8`urql` is a highly customizable and flexible GraphQL client. 9As you use it in your app, it's split into three parts: 10 11- Bindings — such as for React, Preact, Vue, or Svelte — which interact with `@urql/core`'s 12 `Client`. 13- The Client — as created [with the core `@urql/core` package](./basics/core.md), which interacts with "exchanges" to execute GraphQL 14 operations, and which you can also use directly. 15- Exchanges, which provide functionality like fetching or caching to the `Client`. 16 17By default, `urql` aims to provide the minimal amount of features that allow us to build an app 18quickly. However, `urql` has also been designed to be a GraphQL Client 19that grows with our usage and demands. As we go from building our smallest or first GraphQL apps to 20utilising its full functionality, we have tools at our disposal to extend and customize `urql` to 21our liking. 22 23## Using GraphQL Clients 24 25You may have worked with a GraphQL API previously and noticed that using GraphQL in your app can be 26as straightforward as sending a plain HTTP request with your query to fetch some data. 27 28GraphQL also provides an opportunity to abstract away a lot of the manual work that goes with 29sending these queries and managing the data. Ultimately, this lets you focus on building 30your app without having to handle the technical details of state management in detail. 31 32Specifically, `urql` simplifies three common aspects of using GraphQL: 33 34- Sending queries and mutations and receiving results _declaratively_ 35- Abstracting _caching_ and state management internally 36- Providing a central point of _extensibility_ and integration with your API 37 38In the following sections we'll talk about the way that `urql` solves these three problems and how the logic is abstracted away internally. 39 40## Requests and Operations on the Client 41 42If `urql` was a train it would take several stops to arrive at its terminus, our API. It starts with us 43defining queries or mutations by writing in GraphQL's query language. 44 45Any GraphQL request can be abstracted into its query documents and its variables. 46 47```js 48import { gql } from '@urql/core'; 49 50const query = gql` 51 query ($name: String!) { 52 helloWorld(name: $name) 53 } 54`; 55 56const request = createRequest(query, { 57 name: 'Urkel', 58}); 59``` 60 61In `urql`, these GraphQL requests are treated as unique objects and each GraphQL request will have 62a `key` generated for them. This `key` is a hash of the query document and the variables you provide 63and are set on the `key` property of a [`GraphQLRequest`](./api/core.md#graphqlrequest). 64 65Whenever we decide to send our GraphQL requests to a GraphQL API we start by using `urql`'s 66[`Client`](./api/core.md#client). 67The `Client` accepts several options to configure its behaviour and the behaviour of exchanges, 68like the `fetchExchange`. For instance, we can pass it a `url` which the `fetchExchange` will 69use to make a `fetch` call to our GraphQL API. 70 71```js 72import { Client, cacheExchange, fetchExchange } from '@urql/core'; 73 74const client = new Client({ 75 url: 'http://localhost:3000/graphql', 76 exchanges: [cacheExchange, fetchExchange], 77}); 78``` 79 80Above, we're defining a `Client` that is ready to accept our requests. It will apply basic 81document caching and will send uncached requests to the `url` we pass it. 82The bindings that we've seen in [the "Basics" section](./basics/README.md), like `useQuery` for 83React for example, interact with [the `Client`](./api/core.md#client) directly and are a thin 84abstraction. 85 86Some methods can be called on it directly however, as seen [on the "Core Usage" 87page](./basics/core.md#one-off-queries-and-mutations). 88 89```js 90// Given our request and client defined above, we can call 91const subscription = client.executeQuery(request).subscribe(result => { 92 console.log(result.data); 93}); 94``` 95 96As we've seen, `urql` defines our query documents and variables as 97[`GraphQLRequest`s](./api/core.md#graphqlrequest). However, since we have more metadata that is 98needed, like our `url` option on the `Client`, `urql` internally creates [`Operation`s](./api/core.md#operation) 99each time a request is executed. The operations are then forwarded to the exchanges, like the 100`cacheExchange` and `fetchExchange`. 101 102An "Operation" is an extension of `GraphQLRequest`s. Not only do they carry the `query`, `variables`, 103and a `key` property, they will also identify the `kind` of operation that is executed, like 104`"query"` or `"mutation"`, and they contain the `Client`'s options on `operation.context`. 105 106![Operations and Results](./assets/urql-event-hub.png) 107 108This means, once we hand over a GraphQL request to the `Client`, it will create an `Operation`, 109and then hand it over to the exchanges until a result comes back. 110 111As shown in the diagram, each operation is like an event or signal for a GraphQL request to start, 112and the exchanges will eventually send back a corresponding result. 113However, because the cache can send updates to us whenever it detects a change, or you could cancel 114a GraphQL request before it finishes, a special "teardown" `Operation` also exists, which cancels 115ongoing requests. 116 117## The Client and Exchanges 118 119To reiterate, when we use `urql`'s bindings for our framework of choice, methods are called on the 120`Client`, but we never see the operations that are created in the background from our bindings. We 121call a method like `client.executeQuery` (or it's called for us in the bindings), an operation is 122issued internally when we subscribe with a callback, and later, we're given results. 123 124![Operations stream and results stream](./assets/urql-client-architecture.png) 125 126While we know that, for us, we're only interested in a single [`Operation`](./api/core.md#operation) 127and its [`OperationResult`s](./api/core.md#operationresult) at a time, the `Client` treats these as 128one big stream. The `Client` sees an incoming flow of all of our operations. 129 130As we've learned before, each operation carries a `key` and each result we receive carries the 131original `operation`. Because an `OperationResult` also carries an `operation` property the `Client` 132will always know which results correspond to an individual operation. 133However, internally, all of our operations are processed at the same time concurrently. However, from 134our perspective: 135 136- We subscribe to a "stream" and expect to get results on a callback 137- The `Client` issues the operation, and we'll receive some results back eventually as either the 138 cache responds (synchronously), or the request gets sent to our API. 139- We eventually unsubscribe, and the `Client` issues a "teardown" operation with the same `key` as 140 the original operation, which concludes our flow. 141 142The `Client` itself doesn't actually know what to do with operations. Instead, it sends them through 143"exchanges". Exchanges are akin to [middleware in Redux](https://redux.js.org/advanced/middleware) 144and have access to all operations and all results. Multiple exchanges are chained to process our 145operations and to execute logic on them, one of them being the `fetchExchange`, which as the name 146implies sends our requests to our API. 147 148### How operations get to exchanges 149 150We now know how we get to operations and to the `Client`: 151 152- Any bindings or calls to the `Client` create an **operation** 153- This operation identifies itself as either a `"query"`, `"mutation"` or `"subscription"` and has a 154 unique `key`. 155- This operation is sent into the **exchanges** and eventually ends up at the `fetchExchange` 156 (or a similar exchange) 157- The operation is sent to the API and a **result** comes back, which is wrapped in an `OperationResult` 158- The `Client` filters the `OperationResult` by the `operation.key` and — via a callback — gives us 159 a **stream of results**. 160 161To come back to our train analogy from earlier, an operation, like a train, travels from one end 162of the track to the terminus — our API. The results then come back on the same path as they're just 163travelling the same line in reverse. 164 165### The Exchanges 166 167By default, the `Client` doesn't do anything with GraphQL requests. It contains only the logic to 168manage and differentiate between active and inactive requests and converts them to operations. 169To actually do something with our GraphQL requests, it needs _exchanges_, which are like plugins 170that you can pass to create a pipeline of how GraphQL operations are executed. 171 172By default, you may want to add the `cacheExchange` and the `fetchExchange` from `@urql/core`: 173 174- `cacheExchange`: Caches GraphQL results with ["Document Caching"](./basics/document-caching.md) 175- `fetchExchange`: Executes GraphQL requests with a `fetch` HTTP call 176 177```js 178import { Client, cacheExchange, fetchExchange } from '@urql/core'; 179 180const client = new Client({ 181 url: 'http://localhost:3000/graphql', 182 exchanges: [cacheExchange, fetchExchange], 183}); 184``` 185 186As we can tell, exchanges define not only how GraphQL requests are executed and handled, but also 187get control over caching. Exchanges can be used to change almost any behaviour in the `Client`, 188although internally they only handle incoming & outgoing requests and incoming & outgoing results. 189 190Some more exchanges that we can use with our `Client` are: 191 192- [`mapExchange`](./api/core.md#mapexchange): Allows changing and reacting to operations, results, and errors 193- [`ssrExchange`](./advanced/server-side-rendering.md): Allows for a server-side renderer to 194 collect results for client-side rehydration. 195- [`retryExchange`](./advanced/retry-operations.md): Allows operations to be retried on errors 196- [`persistedExchange`](./advanced/persistence-and-uploads.md#automatic-persisted-queries): Provides support for Automatic 197 Persisted Queries 198- [`authExchange`](./advanced/authentication.md): Allows refresh authentication to be implemented easily. 199- [`requestPolicyExchange`](./api/request-policy-exchange.md): Automatically refreshes results given a TTL. 200- `devtoolsExchange`: Provides the ability to use the [urql-devtools](https://github.com/urql-graphql/urql-devtools) 201 202We can even swap out our [document cache](./basics/document-caching.md), which is implemented by 203`@urql/core`'s `cacheExchange`, with `urql`'s [normalized cache, 204Graphcache](./graphcache/README.md). 205 206[Read more about exchanges and how to write them from scratch on the "Authoring Exchanges" 207page.](./advanced/authoring-exchanges.md) 208 209## Stream Patterns in `urql` 210 211In the previous sections we've learned a lot about how the `Client` works, but we've always learned 212it in vague terms — for instance, we've learned that we get a "stream of results" or `urql` sees all 213operations as "one stream of operations" that it sends to the exchanges. 214But, **what are streams?** 215 216Generally we refer to _streams_ as abstractions that allow us to program with asynchronous events 217over time. Within the context of JavaScript we're specifically thinking in terms of 218[Observables](https://github.com/tc39/proposal-observable) 219and [Reactive Programming with Observables.](http://reactivex.io/documentation/observable.html) 220These concepts may sound intimidating, but from a high-level view what we're talking about can be 221thought of as a combination of promises and iterables (e.g. arrays). We're dealing with multiple 222events, but our callback is called over time. It's like calling `forEach` on an array but expecting 223the results to come in asynchronously. 224 225As a user, if we're using the one framework bindings that we've seen in [the "Basics" 226section](./basics/README.md), we may never see these streams in action or may never use them even, 227since the bindings internally use them for us. But if we [use the `Client` 228directly](./basics/core.md#one-off-queries-and-mutations) or write exchanges then we'll see streams 229and will have to deal with their API. 230 231### Stream patterns with the client 232 233When we call methods on the `Client` like [`client.executeQuery`](./api/core.md#clientexecutequery) 234or [`client.query`](./api/core.md#clientquery) then these will return a "stream" of results. 235 236It's normal for GraphQL subscriptions to deliver multiple results, however, even GraphQL queries can 237give you multiple results in `urql`. This is because operations influence one another. When a cache 238invalidates a query, this query may refetch, and a new result is delivered to your application. 239 240Multiple results mean that once you subscribe to a GraphQL query via the `Client`, you may receive 241new results in the future. 242 243```js 244import { gql } from '@urql/core'; 245 246const QUERY = gql` 247 query Test($id: ID!) { 248 getUser(id: $id) { 249 id 250 name 251 } 252 } 253`; 254 255client.query(QUERY, { id: 'test' }).subscribe(result => { 256 console.log(result); // { data: ... } 257}); 258``` 259 260Read more about the available APIs on the `Client` in the [Core API docs](./api/core.md). 261 262Internally, these streams and all exchanges are written using a library called 263[`wonka`](https://wonka.kitten.sh/basics/background), which is a tiny Observable-like 264library. It is used to write exchanges and when we interact with the `Client` it is used internally 265as well.