Query
Load data from the server and subscribe to any changes of fields we detect from mutations, subscriptions, and other queries.
SvelteKit example
query MyProfileInfo { viewer { firstName avatar }}<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}<script> 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.
Svelte store fields
A query store holds an object with the following fields that accessed like $store.data:
datacontains the result of the query. It’s value will update as mutations, subscriptions, and other queries provide more recent information.fetchingcontains the loading state (trueorfalse). See the Loading States section for more info for fine-grained loading states.errorscontains any error values that occur for a query found outside of a route component (ie, not defined insrc/routes). If you want to use this for managing your errors, you should enable the quietQueryError configuration option.partialcontains a boolean that indicates if the result has a partial matchvariablescontains the variables that were sent in the last query, mutation or subscription request.
Svelte store methods
A query store has the following methods that invoked without the $, ie store.fetch(...):
fetchexpectsstore.fetch({ variables: {/* variables */} });fetchis 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, setpolicy: 'NetworkOnly'.
Automatic Loading
SvelteKit 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_loadfrom your+page.jsfile - adding
@loadto an inline query in+page.svelte(remember to use$:) +page.gqlfile 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.
SvelteKit 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:
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' }}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.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' }}Runtime Scalars
Houdini also supports an experimental API for passing query variables from your
application’s session using something we call Runtime Scalars. To enable and configure
this feature, you must provide an object to the runtimeScalars feature config:
export default { // ... features: { runtimeScalars: { OrganizationFromSession: { type: 'ID', resolve: ({session}) => session.organization } } }}With that defined, you can now use it as a scalar in a query and Houdini will call the configured
resolve function to generate the variable value:
query OrganizationInfo($id: OrganizationFromSession!) { organization(id: $id) { name }}Please keep in mind that this feature is still considered experimental and could change with any minor version.
SvelteKit 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.
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:' }}import { graphql } from '$houdini'import { redirect } from '@sveltejs/kit'
export const _houdini_load = graphql(` query AllItems($completed: Boolean) { items(completed: $completed) { id text } }`)
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.
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" }}import { graphql } from '$houdini'import { error } from '@sveltejs/kit'
export const _houdini_load = graphql(` query MyProfile($id: ID!) { profile(id: $id) { name } }`)
export const _MyProfileVariables = ({ params }) => { return { id: params.id }}
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.
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')}import { graphql } from '$houdini'
export const _houdini_load = graphql(` query MyProfile { profile { name } }`)
export function _houdini_onError({ error }) { throw this.redirect(307, '/login')}SvelteKit 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'
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:
<script lang="ts"> import type { PageData } from './$houdini' export let data: PageData
$: ({ MyQuery } = data)</script>
{$MyQuery.data.value}<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'import type { PageLoad } from './$houdini'
export const load: PageLoad = async (event) => { return { ...await load_MyQuery({ event, variables: { variable1: 'value' } }) }}import { load_MyQuery } from '$houdini'
export const load = async (event) => { return { ...await load_MyQuery({ event, variables: { variable1: 'value' } }) }}Loading multiple stores simultaneously
Be careful when loading multiple stores at once.
Each of your loads should be performed inside of an await Promise.all so you perform the queries in parallel.
This can get rather tedious so houdini provides a loadAll function you can use to simplify this process:
import { loadAll, load_MyQuery, load_MyOtherQuery } from '$houdini'import type { PageLoad } from './$houdini'
export const load: PageLoad = async (event) => { return { ...(await loadAll( load_MyQuery({ event }), load_MyOtherQuery({ event }) )) }}import { loadAll, load_MyQuery, load_MyOtherQuery } from '$houdini'
export const load = async (event) => { return { ...(await loadAll(load_MyQuery({ event }), load_MyOtherQuery({ event }))) }}If you want to load the same store twice or customize the props that will get passed to your route, you can pass any combination of single loaders and objects that map the result to a prop value:
import { loadAll, load_MyQuery, load_MyOtherQuery } from '$houdini'import type { PageLoad } from './$houdini'
export const load: PageLoad = async (event) => { return { ...(await loadAll(load_MyQuery({ event }), { propA: load_MyOtherQuery({ event, variables: { foo: 'A' } }), propB: load_MyOtherQuery({ event, variables: { foo: 'B' } }) })) }}import { loadAll, load_MyQuery, load_MyOtherQuery } from '$houdini'
export const load = async (event) => { return { ...(await loadAll(load_MyQuery({ event }), { propA: load_MyOtherQuery({ event, variables: { foo: 'A' } }), propB: load_MyOtherQuery({ event, variables: { foo: 'B' } }) })) }}The above example will provide 3 props to your route: MyQuery containing an instance of MyQuery, propA with an instance of MyOtherQuery with the variables {foo: "A"} loaded, and propB with an instance of MyOtherQuery with the variables {foo: "B"} loaded.
Svelte 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.
Ways to configure blocking behavior
- By configuration with the parameter defaultRouteBlocking.
- If you have throwOnError set with operations “all” or “query”, Houdini will always block.
- If you have a
_houdini_afterLoador_houdini_onErrorset, Houdini will always block. - By passing the arg
blockingin the fetch function you can overwrite the behavior.
Blocking on Load
import { redirect } from '@sveltejs/kit'import type { PageLoad } from './$houdini'
export const load: PageLoad = async (event) => { // blocking:true makes this await "real" const { data } = await MyStore.fetch({ event, blocking: true })
// check the response of the query and redirect when appropriate if (!data.fieldName) { throw redirect(300, '/not-field-name') }
return {}}import { redirect } from '@sveltejs/kit'
export const load = async (event) => { // blocking:true makes this await "real" const { data } = await MyStore.fetch({ event, blocking: true })
// check the response of the query and redirect when appropriate if (!data.fieldName) { throw redirect(300, '/not-field-name') }
return {}}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:
<script lang="ts"> import type { PageData } from './$houdini' export let data: PageData
$: ({ MyQuery } = data)</script>
{#if $MyQuery.fetching} loading...{:else} {$MyQuery.data....}{/if}<script> 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.
Svelte component 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.
<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}<script> import { graphql } from '$houdini'
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.
SvelteKit 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'
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.
SvelteKit 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'
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' }})) }}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 clientexport default new HoudiniClient(requestHandler)const requestHandler = async ({ fetch, text, variables, session, metadata }) => { // do anything with metadata inside of here}
// Export the Houdini clientexport 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 Svelte 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:
Paginated query
query MyFriends { viewer { friends(first: 10) @paginate(name: "My_Friends") { edges { node { id } } } }}↓
Generated store additions
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:
Paginated query
query MyFriends { viewer { friends(limit: 10) @paginate(name: "My_Friends") { id } }}↓
Generated store additions
type MyFriendsStore = QueryStore & { loadNextPage( limit?: number, offset?: number, houdiniContext?: HoudiniContext, ): Promise<void>}Deduplication
By default, nothing stops the same operation from running multiple times simultaneously. The @dedupe directive gives us control over that behavior:
query UserProfile($id: ID!) @dedupe { user(id: $id) { name }}With no arguments, @dedupe prevents a second request from firing if an identical one is already in-flight. To cancel the first request instead of dropping the second, pass cancelFirst: true:
query UserProfile($id: ID!) @dedupe(cancelFirst: true) { user(id: $id) { name }}The match argument controls what counts as “identical”:
Operation— dedupe if any execution of this operation is pending, regardless of variables (default)Variables— dedupe only if the variables also matchNone— never dedupe