Scott Spence

Loading...

Use URQL with Svelte

6 min read
sveltekit how-to svelte

Use the Universal React Query Library (URQL) in Svelte!

Yeah, React and Svelte! Well, not really! There are Svelte bindings in URQL now! So I’m going to take a look at configuring it for use in a Svelte project. If you’re wondering if URQL is right for your project you can check out the features by comparison.

So, standard guide fodder now, spin up a new project and configure the thing.

GraphQL endpoint

So what I’m going to need is a GraphQL endpoint to query, no probs, I’ll spin up a GraphCMS project blog template and use the Content API from that for my endpoint.

Check out the short explainer video if none of that made any sense to you:

Set up the project

Time to spin up a new project, I can do that using the npm init command:

npm init svelte@next sveltekit-with-urql

From the CLI prompts I’ll pick the following options:

✔ Skeleton project
✔ Use TypeScript? › No
✔ Add ESLint for code linting? › No
✔ Add Prettier for code formatting? › Yes

Once that’s finished doing it’s thing I can change into the newly created project (cd) directory and install the dependencies along with the dependencies I’ll need for URQL:

# change into newly created project directory
cd sveltekit-with-urql
# install dependencies
npm i
# install dependencies for URQL
npm i -D @urql/svelte graphql

Now I can start the dev server and start making the changes to use URQL.

URQL client

As URQL is used client side (in the browser after the page has loaded) there’s no need to use <script context="module"> which was what initially confused me when starting out with it!

For the URQL client I’ll need to create it in a place accessable by other pages so the most logical place (to me) is to use the client in a Svelte layout page, I’ll need to create that:

touch src/routes/'__layout.svelte'

I’ll add in the URQL initClient and pass in the endpoint, you can use a .env file here, if you do make sure to add .env to your .gitignore file:

touch .env
echo .env >> .gitignore

As it’s a publically accessible endpoint I’m not going to be too concerned about exposing it publically with the VITE_ variable you can read up on hiding env secrets with SvelteKit for more information.

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

<main>
  <slot />
</main>

<style>
  main {
    position: relative;
    max-width: 640px;
    margin: 0 auto;
    padding: 0 20px;
  }
</style>

Using the URQL client

Now I can use the client in the index page of the project to display the results from a query, for now I’ll just query for the posts, title and slug, I’ll define the query in a qgl tag as postsQuery and pass that to the URQL operationStore then use the URQL query to create a subscription for posts:

<!-- src/routes/index.svelte -->
<script>
  // urql initialization
  import { gql, operationStore, query } from '@urql/svelte'
  const postsQuery = gql`
    query Posts {
      posts {
        title
        slug
      }
    }
  `
  // request policy is cache-first (default)
  const posts = operationStore(postsQuery)
  query(posts)
</script>

The subscription means I can use the $ sign in front of the posts variable to subscribe to changes.

I’ll use some conditional rendering with Svelte to check if the posts are fetching or if there are any errors before using the Svelte each directive to loop through the results of the data from endpoint:

{#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}`}>
          <p>{post.title}</p>
        </a>
      </li>
    {/each}
  </ul>
{/if}

Here’s the full src/routes/index.svelte file:

<script>
  // urql initialization
  import { gql, operationStore, query } from '@urql/svelte'
  const postsQuery = gql`
    query Posts {
      posts {
        title
        slug
      }
    }
  `
  // request policy is cache-first (default)
  const posts = operationStore(postsQuery)
  query(posts)
</script>

{#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}`}>
          <p>{post.title}</p>
        </a>
      </li>
    {/each}
  </ul>
{/if}

<style>
  li {
    list-style: none;
    margin-bottom: 5rem;
  }
</style>

Running the dev server will now give me an unordered list with the post title and a link to the posts route for that slug! That doesn’t exist yet, so if you’re interested in going through that then I’ll add more to that in Building on the example.

Conclusion

I now have URQL configured for use in a SvelteKit project 🎉

This pattern can be used for querying any GraphQL endpoint and displaying the results on the client (the browser).

Building on the example

So I have an index page showing me the results for a query but not much else, so now I can add to the example I currently have to use SvelteKit routing to display the data for a single post.

As I’m using a predefined template from GraphCMS I’m going to pop on over to the GraphCMS playground and define a query for a single post, this will look a little something like this:

query Post($slug: String!) {
  post(where: { slug: $slug }) {
    title
    date
    tags
    content {
      html
    }
  }
}

I can now use that in the posts route to query for the post relating to the $slug variable being passed into the query.

I’ll need to create the posts folder and the [slug].svelte file:

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

To get the slug needed for the GraphQL query I’ll need to get that from the page params using the SvelteKit script context module load function:

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

The <script context="module"> is run before the pae loads and the load function is destructuring out the params parameter, then there’s a further destructuring of the slug from that, the slug can now be passed to the page as props.

Then I can instantiate a new operationStore in the [slug].svelte file passing in that query:

<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
        }
      }
    }
  `;
  const post = operationStore(productQuery, { slug });
  query(post);
</script>

The page slug is passed into the operationStore for the GraphQL variable that is used in the Post query (Post($slug:).

I can then subscribe to the changes with $post, for the sake of brevity I’m going to use a pre tag and stringify the results:

<pre>{JSON.stringify($post, null, 2)}</pre>

Here’s the full file:

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

<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
        }
      }
    }
  `;
  const post = operationStore(productQuery, { slug });
  query(post);
</script>

<pre>{JSON.stringify($post, null, 2)}</pre>

That’s it, the rest of this can be built on for rendering out the post markup the same way as in the index file.