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.js -->

<script>
	export let data

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

{$MyProfileInfo.data.viewerProfile.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.
  • loading contains the loading state (true or false) for a query found outside of a route component (ie, not defined in src/routes)
  • 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

Methods

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

  • 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
  • an inline query in +page.svelte
  • +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

Query variables must be exported from your +page.js files and named after your query. This function takes the same arguments passed to the load function described in the SvelteKit docs. You can return the value from this.error and this.redirect in order to change the behavior of the response. Here is a modified example from the source repository:

// src/routes/[filter]/+page.ts

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 function AllItemsVariables(event): AllItems$input {
	// make sure we recognize the value
	if (!['active', 'completed'].includes(event.params.filter)) {
		return this.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.

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.js

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

// It has access to the same arguments,
// this.error, and this.redirect as the variable functions
export function beforeLoad({ page, session }) {
	if (!session.authenticated) {
		return this.redirect(302, '/login')
	}

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

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.js

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

export function MyProfileVariables({
	page: {
		params: { id }
	}
}) {
	return { id }
}

export function afterLoad({ data }) {
	if (!data.MyProfile) {
		return this.error(404)
	}

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

onError

If defined, the load function will invoked 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.js

export const houdini_load = graphql`
	query MyProfile {
		profile {
			name
		}
	}
`

export function onError({ error }) {
	throw this.redirect(307, '/login')
}

Disabling Inline Query Loading

Sometimes it’s useful to have a lazy query that only fires when you call fetch (for example in a search input). For these scenarios, you will want to turn off the automatic loader using the @houdini directive:

<script>
	const result = graphql`
		query MyQuery @houdini(load: false) {
			viewer {
				id
			}
		}
	`
</script>

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'

export async function load(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 recieve props for each of the stores that you have loaded:

<script>
	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'

export async function load(event) {
  return {
    ...await load_MyQuery({ event, variables: { ... } })
  }
}

If you are defining a manual load and need to look at the result of the fetch, we recommend not using the load_ utility but instead defining your load explicitly:

import { MyQueryStore } from '$houdini'

export async function load(event) {
	const MyQuery = new MyQueryStore()

	const result = await MyQuery.fetch(event)

	console.log('do something with', result)

	return { MyQuery }
}

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 query function to instruct the plugin to generate a client-side equivalent of the load function.

<script>
  import { query, graphql } from '$houdini'

 export function MyComponentQueryVariables({ props }) {
	return { id: props.id }
 }

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

{$store.data.value}

Endpoints

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

import { GQL_MyQuery } from '$houdini'

export async function get(event) {
	const { data } = await GQL_MyQuery.fetch({ event })

	return {
		body: {
			data
		}
	}
}

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:

<script context="module">
  import { browser } from '$app/env'
  import MyQueryStore from '$houdini/stores/MyQuery'

</script>

<script>
  export let variables

  $: browser && MyQueryStore.fetch({ variables, metadata: { ... } })
</script>

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

async function fetchQuery({ fetch, text, variables, session, metadata }) {
	// do anything with metadata inside of here
}

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

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.

Forward cursor pagination

If the decorated field implements cursor-based pagination and provides a first argument, the query store will be generated with an extra method that loads more data 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>,

  pageInfo: Readable<PageInfo>
}

Backwards cursor pagination

If the decorated field implements cursor-based pagination and provides a last argument, the query store will be generated with an extra method that loads more data 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(last: 10) @paginate(name: "My_Friends") {
      edges {
        node {
          id
        }
      }
  }
}


type MyFriendsStore = QueryStore & {
  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>,
}