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.
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 v1npm 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-sveltekitnpm inpm 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 {titleslugdateexcerpttagscoverImage {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 initializationimport { gql, operationStore, query } from '@urql/svelte'const postsQuery = gql`query Posts {posts {titleslugdateexcerpttagscoverImage {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 directorymkdir src/routes/posts# make the [slug].svelte filetouch 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 }) {titledatetagsauthor {nameauthorTitle: titlepicture {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 } = paramsreturn { 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 slugimport { gql, operationStore, query } from '@urql/svelte'const productQuery = gql`query Post($slug: String!) {post(where: { slug: $slug }) {titledatetagscontent {html}coverImage {url(transformation: { image: { resize: { fit: clip, width: 600 } } })}}}`// slug is passed as a parameter in the operationStore functionconst 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 foldermkdir src/lib# make the Post componenttouch 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