Use Apollo Client with SvelteKit to Query a GraphQL API

askrodney

Rodney Lab

Posted on August 9, 2021

Use Apollo Client with SvelteKit to Query a GraphQL API

✨ Use Apollo Client with SvelteKit

In this post we look at how to use Apollo Client with SvelteKit. We will query the GitHub GraphQL API to pull out some data from your repos. If you are new to SvelteKit, you will find this post useful, as we use a few of SvelteKit's idiosyncrasies. These help Svelte offer an improved developer experience. If you come from a React background, then you might well already be familiar with Apollo Client, so I hope you can leverage that knowledge here to get to learn SvelteKit a little quicker. Instead of building yet another todo app, you will create some code in this tutorial which you can leverage to generate a Projects page on your personal developer blog.

Use Apollo Client with SvelteKit: What we're Building - screen capture showing an array of boxes stacked vertically. Each box contains details of a Git Hub repo such as name, number of stars and last updated date.

To follow along, you might find a GitHub account useful. If you don't already have one, just hop over to github.com and click the Sign up for GitHub button. We won't look in too much detail at what GraphQL is here. You might be new to GraphQL, though that is not a barrier. You will find this Introduction to GraphQL useful. If you're ready let's get started.

🔧 GitHub GraphQL API

The GitHub GraphQL API let's you query your repo and other users' public repos. Here we will just be pulling data about your own repo, such as description, last commit and stars. You can use this code on your blog Projects page. Using GitHub to source the information you only ever have to update content in one place. You are kind of using GitHub as a Content Management System (CMS) for you Projects page. For sparkles, you could pull the repo code languages and tags, and, add filters so future employers can see exactly what you coded in each language. There is so much magic you can do with this API. Your imagination is the limit!

GitHub GraphQL API Explorer

You don't have to do this step for the tutorial, though you might find it interesting. GitHub has a GraphiQL explorer for the GitHub API. You can use it to check what GraphQL queries you can make as well as get documentation. If you are extending this project for you own needs, you can generate your queries and mutations in GraphiQL and then paste them into your code editor once you are happy with them. For the Explorer to work, you will need to Sign in to your account and then authorise it to access your GitHub account.

Although you don't need to authorise the Explorer get going on this project, you will need to generate a GitHub personal Access Token. You can see full instructions on the GitHub page. In short, once logged in to GitHub, click your profile icon in the top right corner and select Settings. From the options which appear on the left of the screen, select Developer settings. Next select Personal access tokens followed by clicking the Generate new token button. As the note, you can use sveltekit-graphql-github. For this project you will only need the public_repo permission under repo. Finally scroll down to the bottom of the screen and select Generate token. Make a note of the token which the console displays, you will need this shortly.

Now we have a GitHub personal access token. Why don't we create our SvelteKit project?

🧱 Building our SvelteKit App to Query the GitHub API

If you were creating a blog site, you would start with the SvelteKit Blog Starter. However we are just building a simple (literally) single page app, so we will start from scratch. You might find this useful if this is your very first SvelteKit project (if it is your first, also take a look at the guide on Getting Started with SvelteKit). We start by spinning up a skeleton site. If you prefer yarn or npm, swap out the pnpm command:

pnpm init svelte@next sveltekit-graphql-github && cd $_
pnpm install
pnpm run dev
Enter fullscreen mode Exit fullscreen mode

The app should be up and running now on your machine at http://localhost:3000. If something else is already running on port 3000, don't worry, we'll see how to change ports in a moment. We need a few packages for this project, let's install them all at once:

pnpm i -D @apollo/client @fontsource/fira-sans @fontsource/merriweather
  @sveltejs/adapter-static@next dayjs env-cmd graphql node-fetch sass 
    svelte-preprocess
Enter fullscreen mode Exit fullscreen mode

What do we have here? @apollo/client, graphql and node-fetch will be used to make GraphQL queries. We will use @fontsource/fira-sans, @fontsource/merriweather, dayjs, sass and svelte-preprocess for styling and formatting. @sveltejs/adapter-static@next builds a static SvelteKit site. env-cmd is a handy utility for keeping our secrets secret. Speaking of which, next we will add some environment variables to the app.

Environment Variables

Our app will have a client part and a server part. We will build a static site, which means (in production) the server code only runs once when the static site is being generated. Only the server needs to know our secrets for this project, so we do not prefix them with VITE_ and we use env-cmd to access them. There is a little more explanation of environment variables in the Getting Started with SvelteKit post. Anyway, create a .env file in the project's root folder and paste in GitHub personal access token from the previous section:

GRAPHQL_ENDPOINT="https://api.github.com/graphql"
GITHUB_PERSONAL_ACCESS_TOKEN="PASTE_YOUR_TOKEN_IN_HERE"
Enter fullscreen mode Exit fullscreen mode

The GraphQL endpoint is not really secret in this case, but if you were querying another API which was private, you would definitely not want to add it to you committed repo code. To be able to access the environment variables, we need to modify our npm scripts in package.json:

{
    "name": "sveltekit-graphql-github",
    "version": "0.0.1",
    "scripts": {
        "dev": "env-cmd svelte-kit dev -p 3000",
        "build": "env-cmd svelte-kit build",
        "preview": "svelte-kit preview -p 3000",
        "lint": "prettier --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
        "format": "prettier --write --plugin-search-dir=. ."
    },
Enter fullscreen mode Exit fullscreen mode

Also change the ports in lines 5 and 7 if you need to. Next, we will set up the backend and then add the front end afterwards and finally style it.

Apollo Client Set Up

This Apollo Client code is based on a Stack Overflow answer. As the solution suggests, we will only import the parts of Apollo Client which we need. Make a src/lib/utilities folder in your project and add an apolloClient.js file with this code:

import fetch from 'node-fetch';
import { ApolloClient, HttpLink } from '@apollo/client/core/core.cjs.js';
import { InMemoryCache } from '@apollo/client/cache/cache.cjs.js';
import { setContext } from '@apollo/client/link/context/context.cjs.js';

class Client {
    constructor() {
                if (Client._instance) {
                            }
        Client._instance = this;

        this.client = this.setupClient();
    }

    setupClient() {
        const link = new HttpLink({
            uri: process.env['GRAPHQL_ENDPOINT'],
            fetch
        });
        const authLink = setContext((_, { headers }) => {
            return {
                headers: {
                    ...headers,
                    authorization: `Bearer ${process.env['GITHUB_PERSONAL_ACCESS_TOKEN']}`
                }
            };
        });
        const client = new ApolloClient({
            credentials: 'include',
            link: authLink.concat(link),
            cache: new InMemoryCache()
        });
        return client;
    }
}
Enter fullscreen mode Exit fullscreen mode

We have modified the code from the Stack Overflow question to authorise us on the GitHub API. If you use this code on another project using an API which does not ned authorisation, remove lines 2027 and also change line 30 to read link,.

Query Endpoint

We will configure our app so the user's browser queries an endpoint on our app's server. That endpoint will respond with the GitHub data the client needs to render. This is not quite how things happen in our case as are generating a static app, but this explanation should give a clearer picture of what we are doing here. SvelteKit uses slug-based routing. The client will send a POST request to the endpoint /query/repositories.json, so we need to create a file at src/routes/query/repositories.json.js to answer that request. Create a file at that location and paste this code into it:

import { client } from '$lib/utilities/apolloClient.js';
import { gql } from '@apollo/client/core/core.cjs.js';

export async function post(request) {
    try {
        const { limit } = request.body;
        const query = gql`
            query RepositoriesQuery($limit: Int) {
                viewer {
                    login
                    name
                    repositories(first: $limit, orderBy: { field: STARGAZERS, direction: DESC }) {
                        nodes {
                            id
                            createdAt
                            name
                            description
                            resourcePath
                            pushedAt
                            stargazerCount
                        }
                    }
                }
            }
        `;
        const { data } = await client.query({
            query,
            variables: { limit }
        });
        return {
            body: JSON.stringify({ data })
        };
    } catch (err) {
        console.error('Error: ', err);
        return {
            status: 500,
            error: 'Error receiving data'
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

We only need to respond to POST requests so our file at this endpoint only contains a post function,

  • in line 6 we destructure the limit variable which we will pass in a JSON post from the client. This is the number of posts we want to receive,

  • lines 725 contain the actual query, notice we use the limit variable to supply the first parameter in our query (line 12). You can go town here and add other parameters you want to query for, like repo code languages and tags,

  • lines 2629 is where we use Apollo Client to make the query. If you used hooks with Apollo Client in React, it might be some time since you have last seen this syntax! We don't need mutations here, but if you do need a mutation in one of your projects, the equivalent code would look like this:

const { data } = await client.mutate({
    mutation,
    variables: { limit }
}); 
Enter fullscreen mode Exit fullscreen mode

You would need to define your mutation GraphQL in a similar way to the query we have above.

  • finally in lines 3032, we convert the data to a JSON string and respond to the request.

We go client side next!

Browser Client Page

We only have a single page for this app. First replace the svelte code in src/routes/index.svelte with this, then we will take a closer look:

<script context="module">
    export const prerender = true;
    export async function load({ fetch }) {
        const url = '/query/repositories.json';
        const res = await fetch(url, {
            method: 'POST',
            credentials: 'same-origin',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({
                limit: 5
            })
        });
        const { data } = await res.json();
        if (res.ok) {
            return {
                props: { data }
            };
        }

        return {
            status: res.status,
            error: new Error(`Couldn't load ${url}`)
        };
    }
</script>

<script>
    import dayjs from 'dayjs';
    import relativeTime from 'dayjs/plugin/relativeTime.js';
    export let data;

    dayjs.extend(relativeTime);

    const { login: user, repositories: repoNodes } = data.viewer;
    const { nodes: repos } = repoNodes;
    const repoCount = repos.length;

    const fromNow = (date) => {
        const daysJSDate = dayjs(date);
        return daysJSDate.fromNow();
    };
</script>

<div class="container">
    <div class="content">
        <h1 class="heading">{`${user}'s GitHub`}</h1>

        {#if repoCount > 0}
            {#each repos as repo, index}
                <article aria-posinset={index + 1} aria-setsize={repoCount} class="repo">
                    <h2>
                        {repo.name}
                    </h2>
                    <span>
                        <span class="meta">Updated {fromNow(repo.pushedAt)}</span>
                        {#if repo.stargazerCount > 0}<span class="meta stars">🌟 {repo.stargazerCount}</span>{/if}
                    </span>
                    {#if repo.description != null}
                    <p><a href={`https://github.com/${repo.resourcePath}`}>{repo.description}</a></p>
                    {/if}
                </article>
            {/each}
        {:else}
            <p>No repos found :/</p>
        {/if}
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

If this is the first svelte code you're looking at, you can see it looks like a kind of a superset of HTML.

The code has three blocks. We will add a fourth block for styling in a moment. The first block is a Javascript module. It contains our load function. For SvelteKit the loader runs before the page renders. Here it is used to get the data from the server endpoint we just created. The page should render in your browser (albeit unstyled). Do you see a list of your public repos? Try changing the limit in line 12 from 5 to 1 and saving. As long as you have more than one public repo in your GitHub, you should see we pull back less data in the GraphQL query and only that data is rendered.

Prerendering

Going back to line 2, this doesn't have an impact when we are running in dev mode. When we build the site though, the prerender instruction tells SvelteKit to generate a static site. This means we pull all the data during the build process and never update it. If we add new repos and want them to appear on our page, we just have to rebuild it. Prerendering can generally be used where the same data is presented to every site visitor. The prerendered page should load quicker since we do not need to query for data while the page is loading up, it will already be encoded in the site's HTML. For the prerendered site, the load function only runs during build.

In lines 1719 you see we return a data prop. This is available in the next script block, which runs when the page renders. In line 32 (next block) you see the way we import a prop is by using the export let syntax. See the Svelte tutorial for more on props. This is confusing initially, though you will soon get used to it. The rest of the block is everyday JavaScript you would see in other frameworks.

Rendered Code

The rendered content is in the third block. You can see a few Svelte features here:

  • line 50 opens an if block. This is how we can do conditional rendering in Svelte. We render the code if lines 5164 if there are some repos in the response. Otherwise we render the paragraph in line 67.

  • lines 5164 show how to loop. Looking at line 51, the repos variable refers to an array which is defined in the previous script block. The repo and index variables are temporary loop variables and used to get data from each element as we loop over the array (think something line {repos.map((repo, index)) => <>{renderMe}</>)} if you are used to React).

The remaining code has further examples of conditional rendering and accessing Javascript variables.

Let's add some Sass

I love to use Sass for styling, though you can use tailwind or other tools with SvelteKit. There are a couple of things we need to do to set up Sass. First we will configure SvelteKit to use the preprocessor. Edit sveltekit.config.js:

/** @type {import('@sveltejs/kit').Config} */
import adapter from '@sveltejs/adapter-static';
import preprocess from 'svelte-preprocess';

const config = {
    preprocess: [
        preprocess({
            scss: {
                prependData: "@import 'src/lib/styles/variables.scss';"
            }
        })
    ],
    kit: {
        adapter: adapter(),
        // hydrate the <div id="svelte"> element in src/app.html
        target: '#svelte'
    }
};
Enter fullscreen mode Exit fullscreen mode

Before we look at styling, we also have some code here which tells SvelteKit to generate a static site. You can deploy this site to Cloudflare Pages, Netlify, Render or other providers. We just need to import the static adapter in line 2 and use it in line 14.

Here as well as setting up the preprocessor, in line 9 we make some variables available. To start create this file at src/lib/styles/variables.scss, then paste in the code from variables.scss in the GitHub repo. Now create index.sccs, normalise.css and styles.scss in the same folder and follow the links to find the code you need to paste in.

We will need to import those styles so they are available for our page. On a larger site you would create a layout component and import this data there to make it available to every page on the site. Even though we have a single page on our site, we will do the same, so you can see how it's done. Create a default layout file at src/routes/__layout.svelte:

<script>
    import '$lib/styles/normalise.css';
    import '$lib/styles/index.scss';
    import '@fontsource/merriweather/400.css';
    import '@fontsource/merriweather/700.css';
    import '@fontsource/fira-sans/400.css';
</script>

<slot />
Enter fullscreen mode Exit fullscreen mode

Here we import our the fonts we want to self host (we installed them earlier) as well as the styles. This is a layout file and the <slot /> element in line 9 is a placeholder for our content. If you are using Svelte for the first time, add a <h1> element above it and a paragraph below so you can really see it's a layout template. On a larger site, we would add any headers and footers which appear on every page to this file.

Finally, to complete style, paste these style at the bottom of src/routes/index.svelte

<style lang="scss">
    .container {
        background: $color-theme-4;
        border: solid $color-theme-3 $spacing-px;
        border-radius: $spacing-1;
        margin: 0;
        width: 70%;
        margin: $spacing-12 auto;
        padding: $spacing-2 $spacing-4;

        p {
            font-size: $font-size-2;
            margin-top: $spacing-8;
        }
    }
    .heading {
        color: $color-theme-3;
    }

    .content {
        margin: $spacing-12;
        color: $color-theme-3;
    }

    .repo {
        border: solid $color-theme-3 $spacing-px;
        border-radius: $spacing-1;
        background-color: $color-theme-5;
        margin: $spacing-6 $spacing-0;
        padding: $spacing-4;
        color: $color-theme-3;

        h2 {
            margin-top: $spacing-0;
            margin-bottom: $spacing-4;
            color: $color-theme-3;
            font-size: $font-size-4;
        }

        .stars {
            font-weight: $font-weight-bold;
        }
    }

    .repo:hover {
        background-color: $color-theme-3;
        color: $color-theme-5;

        h2 {
            color: $color-theme-5;
        }

        a {
            color: $color-theme-4;
        }

        .meta {
            border: solid $color-theme-4 $spacing-px;
            padding: #{$spacing-1 - $spacing-px} #{$spacing-2 - $spacing-px};
        }
    }

    .meta {
        font-size: $font-size-1;
        background-color: $color-theme-3;
        color: $color-theme-4;
        padding: $spacing-1 $spacing-2;
        border-radius: $spacing-1;
    }

    @media screen and (max-width: $desktop-breakpoint) {
        .container {
            width: 95%;
        }
        .content {
            margin: $spacing-12 $spacing-2;
        }
        .repo {
            margin: $spacing-10 $spacing-0;
        }
    }
</style>
Enter fullscreen mode Exit fullscreen mode

We tell SvelteKit our styles are using scss in line 71 and then have access to the variable defined in the global styles we created a moment ago.

🔥 How Does in Look?

Normally at this stage in our tutorials, we run some tests. I think all you need to do is refresh your browser and you should see your repos nicely styled. Let me know if something is not working, or if I haven't explained some part well. That way I can update the tutorial for anyone else following later.

🙌🏽 Use Apollo Client with SvelteKit: Wrapup

That's it for this post. We have seen:

  • how to build a SvelteKit site from scratch,
  • that GraphQL offers a convenient way to access GitHub meta,
  • how to use Apollo Client with SvelteKit to query a GraphQL API.

As always suggestions for improvements, together with requests for explanations and feedback are more than welcome. Also let me know what other features you would like implemented on the starter. The full code for this tutorial on how to use Apollo Client with SvelteKit is on the Rodney Lab GitHub repo. There is also a live demo site running on Render.

🙏🏽 Use Apollo Client with SvelteKit: Feedback

Have you found the post useful? Would you like to see posts on another topic instead? Get in touch with ideas for new posts. Also if you like my writing style, get in touch if I can write some posts for your company site on a consultancy basis. Read on to find ways to get in touch, further below. If you want to support posts similar to this one and can spare a couple of dollars, rupees, euros or pounds, please consider supporting me through Buy me a Coffee.

Finally, feel free to share the post on your social media accounts for all your followers who will find it useful. As well as leaving a comment below, you can get in touch via @askRodney on Twitter and also askRodney on Telegram. Also, see further ways to get in touch with Rodney Lab. I post regularly on SvelteKit as well as other topics. Also subscribe to the newsletter to keep up-to-date with our latest projects.

💖 💪 🙅 🚩
askrodney
Rodney Lab

Posted on August 9, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related