Using urql with SvelteKit and Hygraph

In this tutorial, we take a look at how to build a basic blog using urql, SvelteKit, Svelte, Hygraph, and GraphQL.

Scott Spence
URQL with Svelte and hygraph

urql is GraphQL client primarily built for use in React, it's also where it's name comes from (Universal React Query Library) it's a highly customizable lightweight GraphQL client that can be used to query any GraphQL endpoint.

URQL now has Svelte Bindings so, let's take a look at getting it configured with a SvelteKit project.

Create SvelteKit skeletonAnchor

First up, I'll spin up a new SvelteKit project with the npm init command:

# note it's `svelte@next` until SvelteKit goes v1
npm init svelte@next using-urql-with-sveltekit

From the CLI prompts, I'll chose the following Skeleton project > No to Use TypeScript > No to Add ESLint for code linting > Yes to Add Prettier for code formatting.

Now I can follow the rest of the instructions from the CLI, change directory (cd) into the newly created project directory, npm install dependencies, skip the optional step to create a git repo and to run the dev script:

cd using-urql-with-sveltekit
npm i
npm run dev -- --open

Install urqlAnchor

I'll check that the skeleton SvelteKit project is working as expected. If it is, I can kill the development server, install the additional dependencies for urql, and the JavaScript implementation of the GraphQL query language:

npm i -D @urql/svelte graphql

Before I set up the urql client, I'll need a GraphQL endpoint to query against. For this, I'll use the Hygraph blog starter template!

Create Hygraph project from starter templateAnchor

If you're following along and you don't have a Hygraph account already then you can sign up here for a new account; otherwise, log into your existing account and select the Blog starter from the Create a new project section.

Now in the Hygraph project I can go to Settings then select the API Access section and copy the Content API from the Endpoints section. Clicking the URL will copy it to my clipboard.

Create .env fileAnchor

I can add the Content API URL directly to the code in the project but I prefer to use a .env file to store the endpoint URL. I'll create a .env file in the project root directory:

touch .env

I'll also need to add .env to the .gitignore file so it's not committed to git:

echo ".env" >> .gitignore

In the .env file I'll add the following:

VITE_GRAPHQL_URL=https://api-eu-central-1.hygraph.com/v2/myprojectid/master

The VITE_ prefix on the variable name is important.

Create the urql clientAnchor

Now to set up the urql client, I can define the urql client inside some <script> tags in a .svelte file like so:

<script>
import { initClient } from '@urql/svelte'
initClient({
url: import.meta.env.VITE_GRAPHQL_URL,
})
</script>

Note: in SvelteKit there's the option to run the load function, which runs before the component is created. More details on that in the SvelteKit documentation.

There's note of an error in the urql documentation that may occur, Function called outside component initialization.

This can be encountered when trying to use the client in <script context="module"> tags.

urql takes advantage of the Svelte Context API, you can add the client to a parent component which will be shared to it's children, the SvelteKit layout file is a great place to put this.

To use the client in other pages/routes I'll define it once in a __layout.svelte file. I'll need to create that now:

touch src/routes/__layout.svelte

In the src/routes/__layout.svelte file I'll add the following:

<script>
import { initClient } from '@urql/svelte'
initClient({
url: import.meta.env.VITE_GRAPHQL_URL,
})
</script>
<slot />

I'm using the urql convenience function, initClient. This combines the createClient and setClient calls into one. This method internally calls Svelte's setContext function.

This will manage all the GraphQL requests for the project.

Note: import.meta.env is for using environment variables in SvelteKit, you can read up more on Env Variables and Modes in the Vite documentation.

Query data from the Hygraph GraphQL endpointAnchor

Over in my Hygraph Blog project, I'll hop on over to the API Playground and create a new GraphQL query to list out all posts:

query Posts {
posts {
title
slug
date
excerpt
tags
coverImage {
url(transformation: { image: { resize: { fit: clip, width: 600 } } })
}
content {
html
}
}
}

Once I validated that query in the API Playground, I can use that in the SvelteKit project using the urql operationStore function, this creates a Svelte writable store:

In the src/routes/index.svelte file I'll add the following:

<script>
// urql initialization
import { gql, operationStore, query } from '@urql/svelte'
const postsQuery = gql`
query Posts {
posts {
title
slug
date
excerpt
tags
coverImage {
url(transformation: { image: { resize: { fit: clip, width: 600 } } })
}
content {
html
}
}
}
`
const posts = operationStore(postsQuery)
query(posts)
</script>
<pre>{JSON.stringify($posts, null, 2)}</pre>

The <pre> tag is a convenience for displaying the JSON data returned by urql in a human readable format.

The data displayed on the page should look something like this:

{
"stale": false,
"fetching": false,
"data": {
"posts": [
{
"title": "Technical SEO with Hygraph",
"slug": "technical-seo-with-hygraph",
"date": "2020-05-05",
"excerpt": "Get started with your SEO...",
"tags": ["SEO"],
"coverImage": {
"url": "https://media.graphassets.com/resize=fit:clip,width:600/hGRc8RS9RPyr4MfoKzDj",
"__typename": "Asset"
},
"content": {
"html": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit...</p>",
"__typename": "RichText"
},
"__typename": "Post"
}
]
}
}

urql has a fetching boolean property which is set to true when the query is being fetched.

I can use this to do some conditional rendering with Svelte if and each directives; I'll remove the <pre> tag and add the following, it it's place:

{#if $posts.fetching}
<p>Loading...</p>
{:else if $posts.error}
<p>Oopsie! {$posts.error.message}</p>
{:else}
<ul>
{#each $posts.data.posts as post}
<li>
<a href={`/posts/${post.slug}`}>
<figure>
<img src={post.coverImage.url} alt={post.title} />
</figure>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
{#if post.tags}
{#each post.tags as tag}
<div>
<span>{tag}</span>
</div>
{/each}
{/if}
</a>
</li>
{/each}
</ul>
{/if}

Let's quickly break down what's happening here, the $ on $posts is to subscribe to Svelte writable store returned from urql.

I can check to see if the posts are fetching by urql if they are return a loading message, if there's an error return an error message, otherwise return the posts in an unordered list.

Then I can loop through the posts with the Svelte each directive.

Great! That's it, I've created my first query using urql and displayed the results on a page.

I can expand on this now using SvelteKit file based routing to display a single post from the ${post.slug} read on if you're interested in that.

SvelteKit routingAnchor

Now, I want to link to a post from the list on the index page. To do that I'll create a new route in the src/routes for posts then add a [slug].svelte in there:

# make the directory
mkdir src/routes/posts
# make the [slug].svelte file
touch src/routes/posts/'[slug].svelte'

Now, I have a route defined I'll create a GraphQL query in the Hygraph API Playground:

query Post($slug: String!) {
post(where: { slug: $slug }) {
title
date
tags
author {
name
authorTitle: title
picture {
url(transformation: { image: { resize: { fit: clip, height: 50, width: 50 } } })
}
}
content {
html
}
coverImage {
url
}
}
}

To get the $slug variable needed for the GraphQL query I'll need to pull that from the page.params that is passed to the [slug].svelte file.

I can access the page.params from the load function in the [slug].svelte file using <script context="module">, like this:

<script context="module">
export const load = async ({ page: { params } }) => {
const { slug } = params
return { props: { slug } }
}
</script>

Then in some <scrip> tags, I can use the urql operationStore function to create a query using the GraphQL query above passing in the slug variable:

<script>
export let slug
import { gql, operationStore, query } from '@urql/svelte'
const productQuery = gql`
query Post($slug: String!) {
post(where: { slug: $slug }) {
title
date
tags
content {
html
}
coverImage {
url(transformation: { image: { resize: { fit: clip, width: 600 } } })
}
}
}
`
// slug is passed as a parameter in the operationStore function
const post = operationStore(productQuery, { slug })
query(post)
</script>

Now, it's a case of rendering out the post data. As the fields being queried are almost identical to what is being done on the index page I'm going to create a <Post> component that can be used on the index route and the post route.

# make a lib folder
mkdir src/lib
# make the Post component
touch src/lib/post.svelte

I can take the markup used in the index page and add it to the <Post> component:

<script>
export let post;
export let copy;
</script>
<figure>
<img src={post.coverImage.url} alt={post.title} />
</figure>
<h2>{post.title}</h2>
{#if !copy}
<p>{post.excerpt}</p>
{/if}
{#if post.tags}
{#each post.tags as tag}
<div>
<span>{tag}</span>
</div>
{/each}
{/if}
{#if copy}
{@html post.content.html}
{/if}

I'm passing in some props here, the post data and a copy boolean that I can use to conditionally render either the post.excerpt (for the index page) or the post.content.html (for the post page).

ConclusionAnchor

I've created a simple blog using Svelte, SvelteKit, urql, GraphQL, and Hygraph. Using urql to create a GraphQL client that can be shared around the project.

Then defined some queries in the Hygraph API Playground and used them with urql to retrieve data from the Hygraph GraphQL API.

Used SvelteKit's file based routing to display a single post with a GraphQL query to filter on the slug variable being passed to the posts/[slug].svelte file.

Finally, I reused the markup on the index page and moved it into a Svelte component to be used by both the index page and the posts/[slug].svelte file with some conditional rendering.

That's it! hope you found it useful!

The full source code for this example can be found on my GitHub