Skip to content

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, Info.gql and +page.svelte:

src/routes/Info.gql
query Info {
species(id: 1) {
pokedexNumber
name
flavor_text
sprites {
front
}
}
}
src/routes/+page.svelte
<script>
import { InfoStore } from '$houdini'
import { Container, Display, Sprite, Panel } from '~/components'
const Info = new InfoStore()
Info.fetch()
</script>
<Container>
{#snippet left()}
<Panel>
<Display id="species-name">
{$Info.data.species.name}
<span>no.{$Info.data.species.pokedexNumber}</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>
{/snippet}
</Container>

You’re already starting to see some of the very exciting things Houdini offers. Houdini picked up your Info.gql file and generated an InfoStore class that you can import and use to fetch data. The store’s value is reactive, so any time the data changes, your component updates automatically.

GraphQL Explained: Queries

A GraphQL query is a string that describes what information you want from the API. For example, the following defines a query named QueryUserInfo. In Houdini, all documents like queries must be named for reasons that will become more clear later.

query CurrentUserInfo {
current_user {
firstName
}
}

The result of the above query might look something like:

{
"data": {
"current_user": {
"firstName": "Bill"
}
}
}

Notice how the fields in the query match the values? The format of the original query is meant to model the response type.

Fields in a query can take arguments to customize their behavior, for example:

query CurrentUserInfo {
current_user {
# specify the format for the date time stamp
lastLogin(format: "YYYY-MM-DD")
}
}

might return something like this:

{
"data": {
"current_user": {
"lastLogin": "2022-12-25"
}
}
}

For more information on GraphQL Queries, this is a good resource.

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 Info.gql and +page.svelte inside of it:

Terminal window
## This needs to be run at the root of the project
cd src/routes && mkdir "[[id]]" && mv Info.gql +page.svelte "./[[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.

GraphQL Explained: Query Variables

All of the queries we’ve seen so far have had static arguments. However, most of the time you will need to want to give an argument a dynamic value based on something in your application. For example, the text of an input when filtering a list. GraphQL allows us to define references to dynamic values that must be fulfilled when sending the query. These values are known as variables and can be applied to not just queries: mutations and subscription too!

Defining variables for your document looks like the follow:

query MyQuery($variable1: Boolean) {
myField(argument: $variable1)
}

Notice the ($variable1: Boolean)? That’s how we say that the MyQuery query takes one argument, called $variable1, that is a Boolean. All variables in GraphQL must start with a $ (makes the compilers job easier) and are optional by default. In order to mark a variable as required, you have to put ! at the end of the type:

query MyQuery($variable1: Boolean, variable2: String!) {
myField(arg1: $variable1, arg2: $variable2)
}

Now that we have the actual route defined, we will have to change our query so that it can accept a variable. Update Info.gql to look like the following:

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

Next, create a +page.ts+page.js file that reads the id from the URL and passes it to the page component:

src/routes/[[id]]/+page.ts
import { error } from '@sveltejs/kit'
export function load({ params }) {
const id = params.id ? parseInt(params.id) : 1
if (id < 1 || id > 151) {
throw error(400, 'id must be between 1 and 151')
}
return { id }
}
src/routes/[[id]]/+page.js
import { error } from '@sveltejs/kit'
export function load({ params }) {
const id = params.id ? parseInt(params.id) : 1
if (id < 1 || id > 151) {
throw error(400, 'id must be between 1 and 151')
}
return { id }
}

Then update +page.svelte to receive id from the load function and pass it to the store:

src/routes/[[id]]/+page.svelte
<script>
import { InfoStore } from '$houdini'
import { Container, Display, Sprite, Panel } from '~/components'
let { data } = $props()
const Info = new InfoStore()
$effect(() => {
Info.fetch({ variables: { id: data.id } })
})
</script>

Whenever data.id changes (because the user navigated to a different URL), $effect re-runs and fetches the new species. 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 load function 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:

src/routes/[[id]]/+page.svelte
{#snippet right()}
<Panel>
<nav>
<a
href={data.id > 1 ? `/${data.id - 1}` : undefined}
class={data.id <= 1 ? 'disabled' : undefined}
>
previous
</a>
<a href="/{data.id + 1}">next</a>
</nav>
</Panel>
{/snippet}

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 || !$Info.data}
<Container>
{#snippet left()}{/snippet}
{#snippet right()}{/snippet}
</Container>
{:else}
<!-- what we had before -->
{/if}

We check both $Info.fetching and !$Info.data so the loading shell also shows on the very first render before any fetch has completed.

Error Handling

The +page.ts+page.js we created earlier already handles the out-of-bounds case by throwing a 400 error before the fetch fires. If you navigate to /152 you should see SvelteKit’s error page.

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.