Web development

How to use multiple GraphQL endpoints with Urql

June 2, 2021 — minutes read
views

If you are familiar with GraphQL you should already have the need to query third party services data in parallel of your main GraphQL API. In this post I summary how I manage to handle multiple GraphQL API endpoints with a single Urql client.

Introduction

Recently, I had to migrate my Apollo Client stack to another GraphQL library. In facts, the way the library was handling tree parsing and request discovery was the source of performances issues viewable on the Google lighthouse scores, preventing the proper indexing on Google SERP. The library is heavy, and has too many tools that I don't use.

As a replacement, I opted for Urql. This library is 1) based on observables, 2) very light, 3) providing such an opposite approach of the Apollo's one: instead of an "all in one" tool ready for any kind of use case I would have, I compose myself my bunch of tools with additional modules to fit my needs. This approach leaves me with the ability to finely control what is bundled.

One of the features of Apollo Client that I really like is the

Link
composition, allowing me to create multiple
HttpLink
for multiple data endpoints (API). If you are familiar to GraphQL, you may already wanted to query data in parallel of you main GraphQL API endpoint. Unfortunately, I haven't found any information online explaining how to do the same thing using Urql. Most of the answers were "why use multiple sources, it's not a GraphQL way" (which is true, but I don't always have the time to create a GraphQL server to federate multiple GraphQL endpoints).

So, if you want to know if it is possible to manage multiple GraphQL API with Urql and how to do it, you have found the right post.

Requirements: install required packages

Urql is a GraphQL client based on a collection of blocks that connect to each other. As I presented in the introduction, this is what allows us to compose the exhaustive list of tools we will use. Think of these modules as operators in the world of observables. And good news, this is exactly on what the library is built.

These blocks, called

exchanges
, bring the parts that modify the requests to the GraphQL servers.

The order is important and must be logical according to the transformations you want to apply. According to the official documentation, you should always start with synchronous tasks and end with asynchronous ones.

When you configure an Urql client, you have access, by default, to certain exchanges:

  • dedupExchange
    : ensures that identical queries are not executed twice
  • cacheExchange
    - cache the result of the requests
  • fetchExchange
    : takes care of executing the HTTP request (using the
    fetch
    API)

For our problem, we will need an

exchange
that is not present in the Urql core:
authExchange
.

Basically, it allows us to easily configure an authorization system (for example via JWT), but it will mainly be used to change the context of our request on the fly.

Let's start by installing:

# With Yarn
$ yarn add @urql/exchange-auth

# With NPM
$ npm install @urql/exchange-auth
Bash

Then we import it:

import { authExchange } from '@urql/exchange-auth
TypeScript

Creating the GraphQL client

This function should return the Urql client creation method with the correct configuration parameters and the different exchanges you wish to implement:

import { Client, createClient } from 'urql'

export function createGraphqlClient(): Client {
return createClient({
url: 'your-api-url', // The default client url
fetchOptions: {
credentials: 'same-origin',
method: 'GET',
headers: {
Authorization: `Bearer ...`,
'Content-Type': 'application/json',
}
},
preferGetMethod: true,
exchanges: [],
})
}
TypeScript

Then we just need to add "basic" exchanges (read more):

import { ..., dedupExchange, cacheExchange, fetchExchange } from 'urql'

export function createGraphqlClient(): Client {
return createClient({
//...
exchanges: [
dedupExchange,
cacheExchange,
fetchExchange
],
})
}
TypeScript

Add authorization exchange

Once the client is configured with the basic options, we can add

authExchange
.

The overall idea is to copy the way we would use with Apollo Client (using

Link
) to manage multiple API endpoints. (If you want to know more about this method, I personally use Jamal Mashal's post you can find here).

To summarize, we will add to the context of each request an optional

clientName
key, which will be used to add modifications to the rest of the context (request URL, headers if an authentication token is needed)

The

authExchange
takes as parameter an object that requires at least 2 keys:
addAuthToOperation
and
getAuth
.

About

addAuthToOperation
:

addAuthToOperation({ operation }) {
// if clientName is not setted in context, we return the current operation
if (!operation.context.clientName) {
return operation
}
const { clientName, fetchOptions } = operation.context
const options = typeof fetchOptions === 'function' ? fetchOptions() : fetchOptions ?? {}
switch (clientName) {
case 'endpoint-a': {
const headers = {
...options.headers,
Authorization: `Bearer ...`,
}
const context = {
...operation.context,
url: 'endpoint-a-url',
fetchOptions: {
...options,
headers,
}
}
return makeOperation(operation.kind, operation, context)
}
case 'endpoint-b': {
const headers = {
...options.headers,
Authorization: `Bearer ...`,
}
const context = {
...operation.context,
url: 'endpoint-b-url',
fetchOptions: {
...options,
headers,
}
}
return makeOperation(operation.kind, operation, context)
}
default: {
throw new Error(`Unexpected object: ${clientName}`)
return operation
}
}
}
TypeScript

Add as many cases as data sources needed.

For

getAuth
in our example it will be simple:

getAuth: null
TypeScript

If needed, you can add logic, like retrieving a token with a promise for example:

getAuth: async () => {
const token = await getToken()

return { token }
},
addAuthToOperation({ authState, operation }) {
const token = authState.token
// ...
}
TypeScript

Request example with GraphQL client

To use the correct API with the GraphQL client:

function getDataFromEndpointA() {
const { data } = await client.query(myQueryForEndpointA, {}, {
clientName: 'endpoint-a',
}).toPromise()

return data
}

function getDataFromEndpointB() {
const { data } = await client.query(myQueryForEndpointB, {}, {
clientName: 'endpoint-b',
}).toPromise()

return data
}
TypeScript

Request example with React hooks

To use the right source through an Urql client integration with React hooks:

const [{ error, data, fetching }] = useQuery(
myQueryForEndpointA,
{} // this object contains GraphQL request variables,
context: useMemo(() => ({
clientName: 'endpoint-a'
}), []),
})

const [{ error, data, fetching }] = useQuery(
myQueryForEndpointB,
{} // this object contains GraphQL request variables,
context: useMemo(() => ({
clientName: 'endpoint-b'
}), []),
})
TypeScript

Note: remember to use

useMemo
when defining the context to avoid going on an infinite loop with React. (see the issue on Github)

Conclusion

You should now have enough information to set up an Urql client that handles multiple GraphQL API endpoints.

Notes:

  • the current code is not 100% type safe since the

    clientName
    key does not exist in the Typescript typing of the Urql context,

  • adding a data source increases the complexity in your typing management if you use

    graphql-codegen
    . We'll talk about that in a future post.

Just for you I created a Gist to summarize all the code snippets I wrote on this post. Do not hesitate to add a comment or to give your feedback on it.

And if you have any questions (or if you wanna talk) you can contact me on Twitter.

0 views 
Rating: 4.1/5 with more than 2 votes.
Give me your feebackShare on Twitter

Related Q&A

  • What is Urql ?

    Urql is a highly customizable GraphQL client. It is a lighter, more functional and easy to use alternative to Apollo Client. It is designed to be both easy to use for GraphQL novices, and at the same time ready to support applications requiring a more advanced GraphQL infrastructure.

  • Who am I ?

    My name is Antoine Lin, I'm 25 and I live near Paris. I grew up in Seine-et-Marne and I studied at HETIC, Montreuil. I'm a freelance developer, and I work with companies and influencers by creating tailored and optimized digital experiences helping them reaching online goals since 2015.

  • What is my business?

    I'm a front-end developer, I'm in charge of creating web experiences (from the creation of the user journey, to the connection with different services, to the integration of interfaces). I also do some back-end and devops works when needed.

Latest posts

About
Web development
Launching of my blog!
May 18 2021 — 15 min read
views

From the choice of the platform, to the way I manage my data with DatoCMS, or to talk about the various difficulties I've faced. I give you, in this article, an in-depth review about how I launched my blog, antoinelin.com.