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:
query MyProfile {
viewer {
firstName
}
}
<script lang="ts">
import type { PageData } from './$houdini'
export let data: PageData
$: ({ MyProfile } = data)
</script>
<div>
{$MyProfile.data.viewer.firstName}
</div>
<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.
<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} />
<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 thegraphql
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()
:
query MyAwesomeQuery {
viewer {
isAwesome
}
}
import { MyAwesomeQueryStore } from '$houdini'
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:
<script lang="ts">
import { graphql } from '$houdini'
const store = graphql(`
query ViewerSettings {
viewer {
isAwesome
}
}
`)
</script>
<script>
import { graphql } from '$houdini'
const store = graphql(`
query ViewerSettings {
viewer {
isAwesome
}
}
`)
</script>
import { graphql } from '$houdini'
const store = graphql(`
query MyAwesomeQuery {
viewer {
isAwesome
}
}
`)
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:
<script lang="ts">
import type { PageData } from './$houdini'
export let data: PageData
$: ({ UserProfile } = data)
</script>
{$UserProfile.data.viewer.firstName}
<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:
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 })),
}
}
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.
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:
import { ProjectListStore, graphql } from '$houdini'
const userInfo = graphql(`
query UserInfo {
viewer {
firstName
}
}
`)
export const _houdini_load = [new ProjectListStore(), userInfo]
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
:
<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
<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:
import { graphql } from '$houdini'
export const _houdini_load = graphql(`
query UserInfo {
viewer {
firstName
}
}
`)
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:
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
}
}
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:
<script lang="ts">
import { graphql } from '$houdini'
const info = graphql(`
query UserInfo @load {
viewer {
firstName
}
}
`)
</script>
{$info.data.viewer.firstName}
<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.
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:
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:
<script lang="ts">
import type { PageData } from './$houdini'
export let data: PageData
$: ({ UserInfo } = data)
</script>
{$UserInfo.data.viewer.firstName}
<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.