Query

Load data from the server and subscribe to any changes of fields we detect from mutations, subscriptions, and other queries.

src/routes/myRoute/+page.gql
query MyProfileInfo {
    viewer {
        firstName
        avatar
    }
}
src/routes/myRoute/+page.svelte
<script lang="ts">
    import type { PageData } from './$houdini'
    export let data: PageData

    // pull the store reference from the route props
    $: ({ MyProfileInfo } = data)
</script>

{$MyProfileInfo.data.viewer.firstName}
src/routes/myRoute/+page.svelte
<script>
    /* @type { import('./$houdini').PageData } */
    export let data
    
    // pull the store reference from the route props
    $: ({ MyProfileInfo } = data)
</script>

{$MyProfileInfo.data.viewer.firstName}

This example takes advantage of Houdini’s powerful load generator and is just one of many ways to get access to a store that can drive your server-side rendered routes. For more information, check out the Working with GraphQL guide.

Store Fields

A query store holds an object with the following fields that accessed like $store.data:

  • data contains the result of the query. It’s value will update as mutations, subscriptions, and other queries provide more recent information.
  • fetching contains the loading state (true or false). See the Loading States section for more info for fine-grained loading states.
  • errors contains any error values that occur for a query found outside of a route component (ie, not defined in src/routes). If you want to use this for managing your errors, you should enable the quietQueryError configuration option.
  • partial contains a boolean that indicates if the result has a partial match
  • variables contains the variables that were sent in the last query, mutation or subscription request.

Methods

A query store has the following methods that invoked without the $, ie store.fetch(...):

  • fetch expects store.fetch({ variables: {/* variables */} });
  • fetch is a function that can be used to load the most recent data from your API (subject to its Cache Policy). If you want to force the request to always resolve against the API, set policy: 'NetworkOnly'.

Automatic Loading

As described in the Working with GraphQL guide, there are 3 things that you can do to get houdini to create a load function for your route:

  • export _houdini_load from your +page.js file
  • adding @load to an inline query in +page.svelte (remember to use $:)
  • +page.gql file in your route directory

Regardless of which pattern you are using, you will quickly need to customize your query’s behavior. This could be because you need to add variables to the query, or maybe you need to perform some logic before and/or after the data is fetched.

Query Variables

The simplest way to pass values for your query inputs is to use route parameters. If houdini sees that the name for a route parameter matches the name of a query’s input, it will attempt to parse the value and pass it to your query.

Sometimes your query variables cannot be directly pulled from your route’s path. In those cases, you can export a function named after your query from +page.js files to perform whatever logic you need. This function takes the same arguments passed to the load function described in the SvelteKit docs. To return an error, or force a redirect, you can use the same utilities exported from @sveltejs/kit. Here is a modified example from the source repository:

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

export const _houdini_load = graphql(`
    query AllItems($completed: Boolean) {
        items(completed: $completed) {
            id
            text
        }
    }
`)

// This is the function for the AllItems query.
// Query variable functions must be named _<QueryName>Variables.
export const _AllItemsVariables : AllItemsVariables = (event) => {
    // make sure we recognize the value
    if (!['active', 'completed'].includes(event.params.filter)) {
        throw error(400, 'invalid filter')
    }

    return {
        completed: event.params.filter === 'completed'
    }
}
src/routes/[filter]/+page.js
import { graphql } from '$houdini'
import { error } from '@sveltejs/kit'

export const _houdini_load = graphql(`
    query AllItems($completed: Boolean) {
        items(completed: $completed) {
            id
            text
        }
    }
`)

// This is the function for the AllItems query.
// Query variable functions must be named _<QueryName>Variables.
/* @type { import('./$houdini').AllItemsVariables } */
export const _AllItemsVariables = (event) => {
    // make sure we recognize the value
    if (!['active', 'completed'].includes(event.params.filter)) {
        throw error(400, 'invalid filter')
    }

    return {
        completed: event.params.filter === 'completed',
    }
}

Hooks

Sometimes you will need to add additional logic to a component’s query. For example, you might want to check if the current session is valid before a query is sent to the server. In order to support this, Houdini will look for hook functions defined in your +page.js files.

_houdini_beforeLoad

Called before Houdini executes load queries against the server. You can expect the same arguments as SvelteKit’s load function.

src/routes/myRoute/+page.ts
import { graphql } from '$houdini'
import { redirect } from '@sveltejs/kit'
import type { BeforeLoadEvent } from './$houdini'

export const _houdini_load = graphql(`
    query AllItems($completed: Boolean) {
        items(completed: $completed) {
            id
            text
        }
    }
`)

export function _houdini_beforeLoad({ page, session }: BeforeLoadEvent) {
    if (!session.authenticated) {
        throw redirect(302, '/login')
    }

    return {
        message: 'Number of items:'
    }
}
src/routes/myRoute/+page.js
import { graphql } from '$houdini'
import { redirect } from '@sveltejs/kit'

export const _houdini_load = graphql(`
    query AllItems($completed: Boolean) {
        items(completed: $completed) {
            id
            text
        }
    }
`)

/**
 * @param { import('./$houdini').BeforeLoadEvent }
 */
export function _houdini_beforeLoad({ page, session }) {
    if (!session.authenticated) {
        throw redirect(302, '/login')
    }

    return {
        message: 'Number of items:',
    }
}

_houdini_afterLoad

Called after Houdini executes load queries against the server. You can expect the same arguments as SvelteKit’s load function, plus an additional data property referencing query result data. Keep in mind that if you define this hook, Houdini will have to block requests to the route in order to wait for the result. For more information about blocking during navigation, check out the deep dive in this section of the query store docs.

src/routes/myRoute/+page.ts
import { graphql } from '$houdini'
import { error } from '@sveltejs/kit'
import type { AfterLoadEvent, MyProfileVariables } from './$houdini'

export const _houdini_load = graphql(`
    query MyProfile($id: ID!) {
        profile(id: $id) {
            name
        }
    }
`)

export const _MyProfileVariables: MyProfileVariables = ({ params }) => {
    return { id: params.id }
}

export function _houdini_afterLoad({ data }: AfterLoadEvent) {
    if (!data.MyProfile) {
        throw  error(404)
    }

    return {
        message: "Hello I'm"
    }
}
src/routes/myRoute/+page.js
import { graphql } from '$houdini'
import { error } from '@sveltejs/kit'

export const _houdini_load = graphql(`
    query MyProfile($id: ID!) {
        profile(id: $id) {
            name
        }
    }
`)

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

/**
 * @param { import('./$houdini').AfterLoadEvent }
 */
export function _houdini_afterLoad({ data }) {
    if (!data.MyProfile) {
        throw error(404)
    }

    return {
        message: "Hello I'm",
    }
}

_houdini_onError

If defined, the load function will invoke this function instead of throwing an error when an error is received. It receives three inputs: the load event, the inputs for each query, and the error that was encountered. Just like the other hooks, onError can return an object that provides props to the route. If you do define this hook, Houdini will have to block requests to the route in order to wait for the result. For more information about blocking during navigation, check out the deep dive in this section of the query store docs.

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

export const _houdini_load = graphql(`
    query MyProfile {
        profile {
            name
        }
    }
`)

export function _houdini_onError({ error }: OnErrorEvent) {
    throw this.redirect(307, '/login')
}
src/routes/myRoute/+page.js
import { graphql } from '$houdini'

export const _houdini_load = graphql(`
    query MyProfile {
        profile {
            name
        }
    }
`)

/**
 * @param { import('./$houdini').OnErrorEvent }
 */
export function _houdini_onError({ error }) {
    throw this.redirect(307, '/login')
}

Manual Loading

If you are writing your route’s load functions by hand, you’ll want to instantiate a store, calls its fetch method and return the store to your route. 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:

import { load_MyQuery } from '$houdini'
import type { PageLoad } from './$houdini'

export const load: PageLoad = async (event) => {
    return {
        ...(await load_MyQuery({ event }))
    }
}
import { load_MyQuery } from '$houdini'

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

In case you were wondering, the load_ prefix is there so you can autocomplete your loads by just typing load_<tab>. Anyway, with this in place, your route will receive props for each of the stores that you have loaded:

src/routes/myRoute.svelte
<script lang="ts">
    import type { PageData } from './$houdini'
    export let data: PageData

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

{$MyQuery.data.value}
src/routes/myRoute.svelte
<script>
    /* @type { import('./$houdini').PageData } */
    export let data
    
    $: ({ MyQuery } = data)
</script>

{$MyQuery.data.value}

If your query has variables, you can pass them straight to the loader:

import { load_MyQuery } from '$houdini'
import type { PageLoad } from './$houdini'

export const load: PageLoad = async (event) => {
  return {
    ...await load_MyQuery({
        event,
        variables: { variable1: 'value' }
    })
  }
}
import { load_MyQuery } from '$houdini'

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

Loading States

By default, SvelteKit blocks navigation while your queries fetch. While this is great for rendering your application for the initial request, it makes it impossible to build loading states for client-side navigation that have been shown to improve perceived loading time.

In order to circumvent this, your query’s do not actually block by default. For some applications, this is not the desired behavior and so Houdini allows you to configure the behavior the blocking nature of your query’s fetches using a combination of directives and configuration values.

A Simple Loading State

The easiest way to detect when your query is waiting on a network request is to look at the fetching value in the response:

src/routes/myRoute.svelte
<script lang="ts">
    import type { PageData } from './$houdini'
    export let data: PageData

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

{#if $MyQuery.fetching}
    loading...
{:else}
    {$MyQuery.data....}
{/if}
src/routes/myRoute.svelte
<script>
    /* @type { import('./$houdini').PageData } */
    export let data
    
    $: ({ MyQuery } = data)
</script>

{#if $MyQuery.fetching}
    loading...
{:else}
    {$MyQuery.data....}
{/if}

Fine-Grain Loading States

For an overview of building reusable loading states that avoid duplicating your applications structure multiple times, please visit the Loading State guide.

Components Queries

Not every query will be inside of a route. In this situations, you can either work with your query stores directly, or use the same graphql function to instruct the plugin to generate a client-side equivalent of the load function.

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

    export const _MyComponentQueryVariables: MyComponentQueryVariables = ({ props }) => {
        return { id: props.id }
    }

    const store = graphql(`
        query MyComponentQuery($id: ID!) @load {
            user(id: $id) {
                id
            }
        }
    `)
</script>

{$store.data.value}
src/lib/MyComponent.svelte
<script>
    import { graphql } from '$houdini'
    
    /* @type { import('./$houdini').MyComponentQueryVariables } */
    export const _MyComponentQueryVariables = ({ props }) => {
        return { id: props.id }
    }
    
    const store = graphql(`
        query MyComponentQuery($id: ID!) @load {
            user(id: $id) {
                id
            }
        }
    `)
</script>

{$store.data.value}

Using the @load directive instructs Houdini to load that query automatically by generating the appropriate load function.

Server only load

Using a query store inside of server only load function looks very similar to the usual load function: just pass the event you are handed in your route function:

import { MyQueryStore } from '$houdini'
import type { PageServerLoad } from './$houdini'

export const load: PageServerLoad = async (event) => {
    const myQuery = new MyQueryStore()
    const { data } = await myQuery.fetch({ event })

    return { data }
}
import { MyQueryStore } from '$houdini'

/* @type { import('./$houdini').PageServerLoad } */
export const load = async (event) => {
    const myQuery = new MyQueryStore()
    const { data } = await myQuery.fetch({ event })

    return { data }
}

Note that like this you will not have a store in your svelte file but directly your json data in export let data.

API route / endpoint

Using a query store inside of an API route (or endpoint) looks like this:

import { json } from '@sveltejs/kit';
import { MyQueryStore } from '$houdini'
import type { RequestHandler } from './$types';

export const GET: RequestHandler = async (event) => {
    const myQuery = new MyQueryStore()
    const { data } = await myQuery.fetch({ event })

    return json(data);
}
import { json } from '@sveltejs/kit'
import { MyQueryStore } from '$houdini'

/* @type { import('./$types').RequestHandler } */
export const GET = async (event) => {
    const myQuery = new MyQueryStore()
    const { data } = await myQuery.fetch({ event })

    return json(data)
}

Note that like this you will not have a store in your svelte files, you will need to fetch data on your own. More info in the SvelteKit Doc.

Passing Metadata

Sometimes you need to do something very custom for a specific route. Maybe you need special headers or some other contextual information. Whatever the case may be, you can pass a metadata parameter to fetch:

import type { PageLoad } from './$houdini'

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

This value will get forwarded to the network function in your client definition, usually found in src/client.js:

import type { RequestHandler } from './$houdini'

const requestHandler: RequestHandler = async ({ fetch, text, variables, session, metadata }) => {
    // do anything with metadata inside of here
}

// Export the Houdini client
export default new HoudiniClient(requestHandler)
/* @type { import('./$houdini').RequestHandler } */
const requestHandler = async ({ fetch, text, variables, session, metadata }) => {
    // do anything with metadata inside of here
}

// Export the Houdini client
export default new HoudiniClient(requestHandler)

Paginated Queries

If the query contains the pagination directive then the generated store will have extra fields/methods according to the pagination strategy and direction. For more information about pagination in general, check out this guide.

Cursor-based pagination

If the decorated field implements cursor-based pagination the query store will be generated with an extra methods that loads more data in both directions and a field pointing to a store with the current pageInfo object. This extra field can be used to track if there are more pages to load:


query MyFriends {
  viewer {
    friends(first: 10) @paginate( name: "My_Friends") {
      edges {
        node {
          id
        }
      }
  }
}


type MyFriendsStore = QueryStore & {
  loadNextPage(
    pageCount?: number,
    after?: string | number
    houdiniContext?: HoudiniContext,
  ): Promise<void>,

  loadPreviousPage(
    pageCount?: number,
    before?: string | number
    houdiniContext?: HoudiniContext,
  ): Promise<void>,

  pageInfo: Readable<PageInfo>
}

Offset/limit Pagination

If the decorated field implements offset/limit pagination and provides a limit argument, the query store will be generated with an extra methods that lets it load more pages after the one the current one:


query MyFriends {
  viewer {
    friends(limit: 10) @paginate(name: "My_Friends") {
      id
    }
  }
}


type MyFriendsStore = QueryStore & {
  loadNextPage(
    limit?: number,
    offset?: number
    houdiniContext?: HoudiniContext,
  ): Promise<void>,
}