Working with GraphQL

Everyone has different needs from their GraphQL client. That means that in order to provide a high quality developer experience, Houdini supports a few different APIs that overlap. While this is great for the community as a whole, it creates a challenge for newcomers since there isn’t a clear path for how to structure their applications.

Hopefully, by the end of this guide you will have a much better understanding of how to do that.

Our Recommendation

This first part is more of a style guide than detailed summary. Rather than make you sit through a bunch of examples of alternate ways of implementing the same thing over and over, we wanted to jump right to our recommendation. Don’t worry if the comments don’t make sense immediately. Come back and read this list a second time after you are done and hopefully your confusion will be cleared.

Route Queries

The first one is straight forward: define your route queries in a file named +page.gql inside of your route directory. When houdini detects this file, it will handle all of the details for you and your route will receive the query store as a prop with the same name:

src/routes/myProfile/+page.gql

query MyProfile {
    viewer {
        firstName
    }
}
src/routes/myProfile/+page.svelte
<script lang="ts">
    import type { PageData } from './$houdini'

    export let data: PageData

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

<div>
    {$MyProfile.data.viewer.firstName}
</div>
src/routes/myProfile/+page.svelte
<script>
    /* @type { import('./$houdini').PageData } */
    export let data
    
    $: ({ MyProfile } = data)
</script>

<div>
    {$MyProfile.data.viewer.firstName}
</div>

The reason we recommend this pattern is that it provides a nice balance between editing experience, co-location, and reliable directory structure. If a route directory contains a +page.gql file, we immediately know that the route loads data from our API. When you need to start doing more complicated things with your queries like variables or custom logic, head over to plugin docs to see how you can customize the load function that houdini will generate for you.

Fragments

Fragments should be defined inside of your .svelte components using the inline fragment function.

src/lib/components/UserAvatar.svelte
<script lang="ts">
    import { fragment, graphql } from '$houdini'
    import type { UserAvatar } from '$houdini'

    export let user: UserAvatar
    $: data = fragment(user, graphql(`
        fragment UserAvatar on User {
            avatar
        }
    `))
</script>

<img src={$data.avatar} />
src/lib/components/UserAvatar.svelte
<script>
    import { fragment, graphql } from '$houdini'
    
    /* @type { import('$houdini').UserAvatar } */
    export let user
    $: data = fragment(user, graphql(`
        fragment UserAvatar on User {
            avatar
        }
    `))
</script>

<img src={$data.avatar} />

This keeps everything nicely isolated within a single .svelte file and feels like giving your svelte components a whole new superpower.

Mutations

At first, we find its fastest to define your documents directly inside of your components. However, as your app grows you’ll probably want to share those mutations in multiple places. When that happens, it’s easy to pull the document into an external file and import it from $houdini:

import { mutation, graphql } from '$houdini'

const UpdateUser = graphql(`
    mutation UpdateUser($input: UpdateUserInput!) {
        updateUser(input: $input) {
            user {
                firstName
            }
        }
    }
`)


# src/lib/graphql/UpdateUser.gql

mutation UpdateUser($firstName: String!) {
    updateUser(firstName: $firstName) {
        user {
            firstName
        }
    }
}


// src/routes/myProfile/+page.svelte

import { UpdateUserStore } from '$houdini'

const update = new UpdateUserStore()

async function update() {
    await update.mutate({ firstName: "..." } )
}

That’s it!

If that’s enough for you, then I hope you have a great rest of your day. Please come tell us what about you’re building with houdini - we’d love to hear from you! If you want to read more, in the next section we’re going to break down all of the different ways that you can work with your project’s GraphQL Documents.

Working with GraphQL, part 2

Now that we have the recommendation out of the way, we can assume that you are reading this section because you are interested in a deeper summary of houdini’s GraphQL APIs.

Let’s start by going over the core abstraction powering Houdini’s data fetching: Document Stores.

Document Stores

When Houdini detects a graphql document in your project, it generates a Svelte store that you can use to interact with your data. Every document type (query, mutation, fragment, and subscription) generates a different kind of store with different methods.

query ViewerProfile {
    viewer {
        firstName
    }
}

// Generated Store
type ViewerProfileStore = {
    subscribe: Readable<ViewerProfile>
    fetch(...): Promise<void>
}

These stores are your primary tools in a Houdini application. So how do you define your documents so Houdini can do its magic?

Defining Documents

There are two different ways you can define graphql documents:

  • External documents: you write your graphql documents in dedicated graphql files (.gql or .graphql) and interact with stores that you import from $houdini
  • Inline documents: you define your graphql documents inside your .svelte, .ts or .js files and interact with stores that are provided by the graphql tag

The following examples all show query documents but everything holds true for all document types (queries, mutations, fragments, and subscriptions).

External Documents

External documents are pretty self explanatory: define your graphql documents a file (one definition per file) and then import your store from $houdini as new MyAwesomeQueryStore():

src/lib/queries/MyAwesomeQuery.gql
query MyAwesomeQuery {
    viewer {
        isAwesome
    }
}
src/routes/myRoute/+page.ts
import { MyAwesomeQueryStore } from '$houdini'
src/routes/myRoute/+page.js
import { MyAwesomeQueryStore } from '$houdini'

Inline Documents

Another option is to define your document directly inside of your source code (ie, in a .svelte, .js, or .ts file). To do that, import graphql from $houdini and pass your query:

src/routes/myRoute/+page.svelte
<script lang="ts">
    import { graphql } from '$houdini'

    const store = graphql(`
        query ViewerSettings {
            viewer {
                isAwesome
            }
        }
    `)
</script>
src/routes/myRoute/+page.svelte
<script>
    import { graphql } from '$houdini'
    
    const store = graphql(`
        query ViewerSettings {
            viewer {
                isAwesome
            }
        }
    `)
</script>
src/routes/myRoute/+page.ts
import { graphql } from '$houdini'

const store = graphql(`
    query MyAwesomeQuery {
        viewer {
            isAwesome
        }
    }
`)
src/routes/myRoute/+page.js
import { graphql } from '$houdini'

const store = graphql(`
    query MyAwesomeQuery {
        viewer {
            isAwesome
        }
    }
`)

More Information

For more information on each store type, please visit the api docs.

Loading Data

For the rest of the guide, we are going to focus on SvelteKit routes. If you want more information on fragments, mutations, or subscriptions, you can head over to the api docs and come back.

Once you have your store, the next step is to start using it. Usually, your route’s .svelte files will receive their stores as props. This often looks something like:

src/routes/myProfile/+page.svelte
<script lang="ts">
    import type { PageData } from './$houdini'

    export let data: PageData

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

{$UserProfile.data.viewer.firstName}
src/routes/myProfile/+page.svelte
<script>
    /* @type { import('./$houdini').PageData } */
    export let data
    
    $: ({ UserProfile } = data)
</script>

{$UserProfile.data.viewer.firstName}

Of course, you could have also defined an inline store, but most of the time, you’ll want to render your views on the server which means we have to define a load function in +page.js. This leads us to the next question: do you want Houdini to generate the load function for you?

Manual Loads

If you are the kind of person who doesn’t like hidden details, you are totally free to define your own load function as long as you call the store’s fetch method. In order to streamline this, Houdini provides a function for each of your stores that you can use to render your route on the server. These functions take the same parameters as fetch:

src/routes/myProfile/+page.ts
import { load_MyQuery } from '$houdini'
import type { PageLoad } from './$houdini'

export const load: PageLoad = async (event) => {
    return {
        ...(await load_MyQuery({ event }))
    }
}
src/routes/myProfile/+page.js
import { load_MyQuery } from '$houdini'

/* @type { import('./$houdini').PageLoad } */
export const load = async (event) => {
    return {
        ...(await load_MyQuery({ event })),
    }
}

If even that is too much magic for you, rest assured all it’s doing is instantiating a store and calling .fetch. You can check out the definition yourself, its defined in $houdini/plugins/houdini-svelte/stores/MyStore.js. If you still want to do something custom, you are free to structure things however you want just make sure that you instantiate stores inside of load and pass them to your route’s .svelte file as props.

Careful!

Be careful when loading multiple stores in a single load. You want to make sure that you aren’t blocking one request while you wait for another. For help in this situation, check out the loadAll function. It’s described inside the Deep Dive, here.

Automatic Loads

On the other hand, maybe you are the kind of person who gets really tired of typing the same code over and over. For those people, Houdini’s vite plugin offers some powerful features that dramatically reduce the amount of boilerplate that you are responsible for. As you’ll see there are a few different ways to get houdini to generate a load for you but they all behave the exact same and can be customized the same way.

The first way to tell houdini to generate a load for you is by exporting a _houdini_load value from +page.js that contains one or more query stores that you want to use in your route:

src/routes/myProfile/+page.ts
import { ProjectListStore, graphql } from '$houdini'

const userInfo = graphql(`
    query UserInfo {
        viewer {
            firstName
        }
    }
`)

export const _houdini_load = [new ProjectListStore(), userInfo]
src/routes/myProfile/+page.js
import { ProjectListStore, graphql } from '$houdini'

const userInfo = graphql(`
    query UserInfo {
        viewer {
            firstName
        }
    }
`)

export const _houdini_load = [new ProjectListStore(), userInfo]

With that in place, your route will be passed two props, named after the queries: ProjectList and UserInfo:

src/routes/myProfile/+page.svelte
<script lang="ts">
    import type { PageData } from './$houdini'

    export let data: PageData

    $: ({ ProjectList, UserInfo } = data)

    $: firstName = $UserInfo.data.viewer.firstName
    $: length = $ProjectList.data.projects.length
</script>

{firstName} can see {length} projects
src/routes/myProfile/+page.svelte
<script>
    /* @type { import('./$houdini').PageData } */
    export let data
    
    $: ({ ProjectList, UserInfo } = data)
    
    $: firstName = $UserInfo.data.viewer.firstName
    $: length = $ProjectList.data.projects.length
</script>

{firstName} can see {length} projects

Keep in mind that _houdini_load can also take a single store, which allows for something interesting:

src/routes/myProfile/+page.ts
import { graphql } from '$houdini'

export const _houdini_load = graphql(`
    query UserInfo {
        viewer {
            firstName
        }
    }
`)
src/routes/myProfile/+page.js
import { graphql } from '$houdini'

export const _houdini_load = graphql(`
    query UserInfo {
        viewer {
            firstName
        }
    }
`)

Your route will be rendered with a single prop: UserInfo. This approach works nicely when you have lots of different variables and hooks for a particular query since it allows all of that logic to be co-located:

src/routes/myProfile/+page.ts
import { graphql } from '$houdini'
import type { UserInfoVariables } from './$houdini'

export const _houdini_load = graphql(`
    query UserInfo($id: ID!) {
        user(id: $id) {
            firstName
        }
    }
`)

export const _UserInfoVariables: UserInfoVariables = ({ params }) => {
    return {
        id: params.id
    }
}
src/routes/myProfile/+page.js
import { graphql } from '$houdini'

export const _houdini_load = graphql(`
    query UserInfo($id: ID!) {
        user(id: $id) {
            firstName
        }
    }
`)

/* @type { import('./$houdini').UserInfoVariables } */
export const _UserInfoVariables = ({ params }) => {
    return {
        id: params.id,
    }
}

Inline Queries

Another way to get a generated load for queries, is to add @load to the document inside of a svelte component:

src/lib/UserList.svelte
<script lang="ts">
    import { graphql } from '$houdini'

    const info = graphql(`
        query UserInfo @load {
            viewer {
                firstName
            }
        }
    `)
</script>

{$info.data.viewer.firstName}
src/lib/UserList.svelte
<script>
    import { graphql } from '$houdini'
    
    const info = graphql(`
        query UserInfo @load {
            viewer {
                firstName
            }
        }
    `)
</script>

{$info.data.viewer.firstName}

This is the recommended approach for non-route components that don’t care about server-side rendering.

Careful!

Route components that use an inline query must use a reactive statement when defining their store:

src/routes/users/+page.svelte
<script lang="ts">
    import { graphql } from '$houdini'

    $: info = graphql(`
        query UserInfo @load {
            viewer {
                firstName
            }
        }
    `)
</script>

{$info.data.viewer.firstName}
src/routes/users/+page.svelte
<script>
    import { graphql } from '$houdini'
    
    $: info = graphql(`
        query UserInfo @load {
            viewer {
                firstName
            }
        }
    `)
</script>

{$info.data.viewer.firstName}

Inline queries also work well for simple routes since we can co-locate our data needs close to the query without having to move between files. Unfortunately, the moment we want to do anything extra (variables, hooks, etc), that logic has to be defined inside of +page.js so routes using this pattern can feel a little scattered at times.

Page Queries

The final approach for loading your data that we need to cover is by defining a page query. A page query is a query defined inside of a +page.gql file inside of your route directory. This file behaves like the rest of SvelteKit’s +page files and automatically configures your route to load the provided query. For example, with the following query defined:

src/routes/myProfile/+page.gql
query UserInfo {
    viewer {
        firstName
    }
}

You can now define a +page.svelte file that looks like the following and your route will be rendered on the server without any extra configuration:

src/routes/myProfile/+page.svelte
<script lang="ts">
    import type { PageData } from './$houdini'

    export let data: PageData

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

{$UserInfo.data.viewer.firstName}
src/routes/myProfile/+page.svelte
<script>
    /* @type { import('./$houdini').PageData } */
    export let data
    
    $: ({ UserInfo } = data)
</script>

{$UserInfo.data.viewer.firstName}

Remember, if you want to define variable functions or hooks, those will go in +page.js.

Layout Queries

Very similar to page queries, you can create a file +layout.gql that will fill the prop data in your layout.svelte file.

That’s all for now!

Thanks for making it this far. Hopefully you have some idea for how you want to structure your application. If not, head to the top of this guide and start with our suggestion, we think its a pretty compelling experience for building GraphQL applications.