Fetching Data

Before we do anything too complicated, lets start with some static content pulled from our GraphQL API. Create two files inside of your src/routes directory, +page.gql and +page.svelte:

src/routes/+page.gql
query Info {
    species(id: 1) {
        id
        name
        flavor_text
        sprites {
            front
        }
    }
}
src/routes/+page.svelte
<script lang="ts">
    import { Container, Display, Sprite, Panel } from '~/components'
    import type { PageData } from './$houdini'

    export let data: PageData

    $: ({ Info } = data)
</script>

<Container>
    <Panel slot="left">
        <Display id="species-name">
            {$Info.data.species.name}
            <span>no.{$Info.data.species.id}</span>
        </Display>
        <Sprite
            id="species-sprite"
            src={$Info.data.species.sprites.front}
            speciesName={$Info.data.species.name}
        />
        <Display id="species-flavor_text">
            {$Info.data.species.flavor_text}
        </Display>
    </Panel>
</Container>
src/routes/+page.svelte
<script>
    import { Container, Display, Sprite, Panel } from '~/components'
    
    /* @type { import('./$houdini').PageData } */
    export let data
    
    $: ({ Info } = data)
</script>

<Container>
    <Panel slot="left">
        <Display id="species-name">
            {$Info.data.species.name}
            <span>no.{$Info.data.species.id}</span>
        </Display>
        <Sprite
            id="species-sprite"
            src={$Info.data.species.sprites.front}
            speciesName={$Info.data.species.name}
        />
        <Display id="species-flavor_text">
            {$Info.data.species.flavor_text}
        </Display>
    </Panel>
</Container>

You’re already starting to see some of the very exciting things Houdini offers. Just like you might define a route’s load function in a standard SvelteKit application, you can use +page.gql to define the query for your route.

The data for the query is passed as a store that’s available in a key that matches its name (in this case its Info). We used the store to render some basic information about Bulbasaur using some components that were provided in the project’s component directory.

If everything is set up properly, you should see a message printed in your terminal once you save the file. Behind the scenes, Houdini is constantly validating and processing your queries so you can catch errors as quickly as possible.

Anyway, now that you have the necessary files, you should see Bulbasaur’s description. If you are still running into issues, please reach out to us on the svelte discord and we’d be happy to help.

Query Variables

This is a good start but we will need to be able to show information for more species than just Bulbasaur. Let’s set up our application to take look at the url for the id of the species we are interested in. To do that, add a directory named [[id]] and move both +page.gql and +page.svelte inside of it:

# This needs to be run at the root of the project
cd src/routes && mkdir "[[id]]" && mv +page.* "./[[id]]"

The double braces might seem strange but that will let us have an optional parameter in the url so we can render the same view for both / and /1.

Now that we have the actual route defined, we will have to change our query so that it can accept a variable. Doing this is relatively simple, just update the query inside of +page.gql to look like the following:

src/routes/[[id]]/+page.gql
query Info($id: Int! = 1) {
    species(id: $id) {
        name
        flavor_text
        sprites {
            front
        }
    }
}

And that’s it! Notice that the route parameter matched the name of the query input? Houdini detected that and took care of the all of the wiring for you. Pretty cool, huh? There is also a way to perform custom logic to compute your query inputs when you need it but we’ll keep things simple for now. For more information, check out the query api docs.

You should be able to navigate to /6 and see Charizard’s information. If you then navigate back to /, there is no value for the [[id]] portion of the url and the query uses its default value of 1.

For completeness, let’s quickly add some buttons to navigate between the different species. Copy and paste this block of code as the last child of the Container component. Don’t worry if you see an error when you click on them, we’ll fix that next.

src/routes/[[id]]/+page.svelte
<Panel slot="right">
    <nav>
        <a href={$Info.data.species.id - 1} disabled={$Info.data.species.id <= 1}>
            previous
        </a>
        <a href={$Info.data.species.id + 1} disabled={$Info.data.species.id >= 151}>
            next
        </a>
    </nav>
</Panel>

Loading State

If you’ve already clicked on those links you probably saw a scary message about accessing a value on null. This is because we haven’t done anything to handle the loading state for our view. Let’s just do something quick and dirty:

src/routes/[[id]]/+page.svelte
{#if $Info.fetching}
    <Container/>
{:else}
    <!-- what we had before -->
{/if}

Error Handling

So far so good! There is one slight problem: there are only 151 species in the first generation of Pokémon. The buttons we added in the last section prevent the user from going beyond those bounds, but if we navigate to /152 directly we will get an error since $Info.data.species is null. Go ahead, give it a try.

In order to prevent this, we need to check if id is between 1 and 151 and if not, render an error for the user. The best way to do this is to use a load hook to check the value before the load fires. Load hooks belong in +page.js files so create a file at src/routes/[[id]]/+page.js that looks like the following:

src/routes/[[id]]/+page.ts
import { error } from '@sveltejs/kit'
import type { BeforeLoadEvent } from './$houdini'

export function _houdini_beforeLoad({ params }: BeforeLoadEvent) {
    // if we were given an id, convert the string to a number
    const id = params.id ? parseInt(params.id) : 1

    // check that the id falls between 1 and 151
    if (id < 1 || id > 151) {
        // return a status code 400 along with the error
        throw error(400, 'id must be between 1 and 151')
    }
}
src/routes/[[id]]/+page.js
import { error } from '@sveltejs/kit'

/**
 * @param { import('./$houdini').BeforeLoadEvent }
 */
export function _houdini_beforeLoad({ params }) {
    // if we were given an id, convert the string to a number
    const id = params.id ? parseInt(params.id) : 1

    // check that the id falls between 1 and 151
    if (id < 1 || id > 151) {
        // return a status code 400 along with the error
        throw error(400, 'id must be between 1 and 151')
    }
}

Load hooks in houdini all begin with _houdini_ and there are a lot more than just beforeLoad. For an overview of what hooks you can define, check out the query api docs.

What’s Next?

Now that you’ve seen the basics of fetching data from the server, we’re going to start to dig a little deeper into how we should be organizing our queries. In the next section we’re going to give our Svelte components the power to define their own data requirements so we don’t have to worry about their concerns when building this route’s query.