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, +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'
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.

GraphQL: 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.

What happened to load?

If you were looking carefully, you might have noticed that we didn’t define a load function as described in the SvelteKit docs. Don’t worry, this route is still rendered on the server thanks to the vite plugin. One of its responsibilities is moving the actual fetch into a load. You can think of the block at the top of this section as equivalent to:

src/routes/+page.svelte
src/routes/+page.ts
import { InfoStore } from '$houdini/stores/Info'
import type { PageLoad } from './$types'
export const load: PageLoad = async ({ event }) => {
const store = new InfoStore()
await InfoStore.fetch({ event })
return {
Info: store,
}
}
src/routes/+page.js
import { InfoStore } from '$houdini/stores/Info'
export const load = async ({ event }) => {
const store = new InfoStore()
await InfoStore.fetch({ event })
return {
Info: store,
}
}

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:

Terminal window
## 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.

GraphQL: 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. 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'
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.