Exploring JavaScript Client Libraries for GraphQL
If you want to interact with a GraphQL API via your frontend web application, you have a ton of options. GraphQL.org currently lists 11 client libraries for JavaScript and, of course, you can use plain old JavaScript. In this post, I'll review some of the options for GraphQL client libraries in JavaScript and some of the features that make each of them unique.
The sample code for this post can be found here. It features the same basic Next.js site built using each of these libraries. While it only covers the basic features of making a GraphQL API request, it can illustrate some of the differences of how each one of these libraries is used. (Note: to run the samples locally, you'll need to create a .env
file and define a GITHUB_API_TOKEN
with a personal access token that you can generate here).
GraphQL Request
GraphQL Request is a library created and maintained by Prisma Labs. I'm choosing to start with this library because it is intentionally designed to be the "minimal", meaning it doesn't include a lot of extra functionality that you'll find in some of the other clients. Basically, GraphQL Request gives you only what you need to easily send and receive GraphQL queries and mutations in the browser or in Node and intentionally little more. This does make GraphQL Request very lightweight and perhaps all you need in many cases.
Let's look at a couple of simple examples. To perform a basic query, all you need to do is npm install and then import graphql-request:
import { request, gql, GraphQLClient } from 'graphql-request';
Next, you just pass your API URL and the GraphQL query you want to execute.
const query = gql`
{
continents {
name
code
}
}
`;
const results = await request('https://countries.trevorblades.com/', query);
To pass a variable, it's just a matter of providing a variables argument as well containing the JSON with the variables:
const query = gql`
query getContinent($code: ID!) {
continent(code: $code) {
name
countries {
name
}
}
}
`;
const variables = {
code: code,
};
const results = await request(
'https://countries.trevorblades.com/',
query,
variables
);
If you need to pass authorization headers to the GraphQL API, you can instantiate the GraphQLClient
with the endpoint URL and header variables. Once this is done, you can call the request()
method on that client.
const graphQLClient = new GraphQLClient('https://api.github.com/graphql', {
headers: {
authorization: 'bearer ' + process.env.GITHUB_API_TOKEN,
},
});
const query = gql`
{
viewer {
name
twitterUsername
bio
}
}
`;
const results = await graphQLClient.request(query);
Mutations using GraphQL Request are essentially a combination of these three items (i.e. the query with variables and an authorization header). GraphQL Request does have a few other minor features, including support for file uploads that you can read about in the documentation
Apollo Client
Apollo Client is created by Apollo and is the most popular GraphQL client library available. While it can obviously do all of the basics like querying, mutations, variables and such, it doubles as a state management library. You can use Apollo Client to manage local state irrespective of whether you have a GraphQL API to connect to or not. However, the real power comes with the ability to cache the state that you retrieve from the remote API and combine that with additional local application state. All of this is built with React in mind, so, while you don't have to use React, Apollo Client integrates easily with it.
Honestly, there's too much to Apollo Client to cover in great detail here, but let's go over some of the basics of querying. Once you have npm installed Apollo Client, you'll need to import three modules in order to make a basic query.
import { gql, ApolloClient, InMemoryCache } from '@apollo/client';
The InMemoryCache
allows you to configure and control Apollo Client's caching strategies. This is particularly useful if you are pulling data on the client. Apollo Client will use the cache wherever it finds that a query has not changed, meaning you will be able to serve responses much faster than reretrieving results over the network. In my example, I'm actually loading content from an API that is being passed via Next.js' getStaticProps()
method. Since this passes data at build-time, the caching strategy isn't really relevant, but it is still required, so we'll just use the defaults.
const client = new ApolloClient({
uri: 'https://countries.trevorblades.com/',
cache: new InMemoryCache(),
});
const results = await client.query({
query: gql`
{
continents {
name
code
}
}
`,
});
Passing variables is pretty straightforward and just a matter of adding the variables key to the query()
call.
const results = await client.query({
query: gql`
query getContinent($code: ID!) {
continent(code: $code) {
name
countries {
name
}
}
}
`,
variables: {
code: code,
},
});
Apollo Client allows for a lot of fine-grained control over the HTTP calls you make using Apollo Link, including adding authorization via a Context Link. However, for our simple purposes, passing a credentials via a standard request.
const client = new ApolloClient({
uri: 'https://api.github.com/graphql',
cache: new InMemoryCache(),
headers: {
authorization: 'bearer ' + process.env.GITHUB_API_TOKEN,
},
});
These basics really don't do Apollo Client justice as, if you want just the core querying features, you can just use GraphQL Request. Since Apollo Client is built with React in mind, many of its key features are geared towards building React single page applications (SPAs). For instance, it comes with a lot of built-in features and components that allow you to directly hydrate your frontend with new data via a GraphQL API using React Hooks. You can also maintain local state that includes both data returned from your GraphQL APIs as well as any other state management. This is powerful enough that I've seen articles arguing for dropping Redux in favor of Apollo Client. To get the full scope of capabilities, check out the Apollo Client documentation.
Urql
Urql seems to sit somewhere in between GraphQL Request and Apollo Client, having more features and capabilities than the former but fewer than the latter, which makes it much more lightweight than Apollo Client. For example, it does include a highly-configurable caching layer similar to Apollo Client, but it does not include local state management. It also has integrations for theReact, Svelte and Vue frameworks built-in (there's also a package for Next.js). If you're looking for a feature-by-feature comparison with Apollo Client, they have one here.
Let's look at doing the basics with Urql here as well. Keep in mind that this sample pulls all its data during build-time, so the framework-specific hooks don't really apply. Instead, we'll just use Urql for simple querying, starting with a basic query. First, we need to import the proper modules.
import { createClient } from 'urql';
Next, we create the client and the query and then pass that to the client's query()
method.
const client = createClient({
url: 'https://countries.trevorblades.com/',
});
const query = `
{
continents {
name
code
}
}
`;
const results = await client.query(query).toPromise();
You'll note that because we are using async/await, we need to convert the stream that query returns into a JavaScript promise.
Passing variable is pretty much what you'd expect — just add them to the query call.
const query = `
query getContinent($code: ID!) {
continent(code: $code) {
name
countries {
name
}
}
}
`;
const variables = {
code: code,
};
const results = await client.query(query, variables).toPromise();
To make a request with an authorization, we need to use the fetchOptions
parameter when calling createClient()
and pass in an object containing our authorization header.
const client = createClient({
url: 'https://api.github.com/graphql',
fetchOptions: {
headers: { authorization: 'bearer ' + process.env.GITHUB_API_TOKEN },
},
});
As with Apollo Client, there's a lot more available within Urql than we can cover here including modules to support server-side rendering (SSR), advanced authentication, retrying operations and caching. To learn more check the documentation.
Connecting to StepZen
Of course, StepZen is all about creating GraphQL backends that you can use on your frontend projects. Once you've created an API using StepZen, you'll need to call it using an authenticated Request with your StepZen API key. Here are a few examples of setting it up. (Note that these assume a .env
file that defines STEPZEN_API_KEY
)
GraphQL Request
const graphQLClient = new GraphQLClient('https://account-name.stepzen.net/folder-name/api-name/__graphql', {
headers: {
authorization: 'Apikey ' + process.env.STEPZEN_API_KEY,
},
});
Apollo Client
const client = new ApolloClient({
uri: 'https://account-name.stepzen.net/folder-name/api-name/__graphql',
cache: new InMemoryCache(),
headers: {
authorization: 'Apikey ' + process.env.STEPZEN_API_KEY,
},
});
Urql
const client = createClient({
url: 'https://account-name.stepzen.net/folder-name/api-name/__graphql',
fetchOptions: {
headers: { authorization: 'Apikey ' + process.env.STEPZEN_API_KEY },
},
});
Which One Should I Use?
You may be wondering, which of these is the right one to use for my project? The answer, perhaps unsurprisingly, is: it depends. However, here are some guidelines for making the choice:
- If you are primarily looking for convenience methods for querying a GraphQL API, but have no need for things like caching, framework integration, or state management, then GraphQL Request offers an easy to use and lightweight solution. For example, in my case, I was querying for a Jamstack application where the data was being integrated at build-time, so none of these features are really relevant for this (even though my example uses the React-based Next.js framework).
- If you are integrating with a framework like React, Vue or Svelte or you want to add in a cache for client-side API calls but you don't necessarily need the full state management solutions that Apollo Client provides, then go with Urql for something more lightweight.
- If you need the most comprehensive solution, including the state management, or you want the deepest integration with React, then Apollo Client is a good choice.
Of course, there are a bunch of other libraries beyond the ones that I discussed here. Check out this list on GraphQL.org for a more complete list.