Your GraphQL Server
If your backend lives in the same codebase (or doesn’t exist yet), Houdini includes a built-in GraphQL server. Export a schema, and Houdini wraps it in a Yoga instance, serves it at /_api, and points the client at it automatically. No URL configuration required.
Setup
Export an executable schema from src/server/+schema:
import { makeExecutableSchema } from '@graphql-tools/schema'
export default makeExecutableSchema({ typeDefs: ` type Query { hello: String! } `, resolvers: { Query: { hello: () => 'world' } }})import { makeExecutableSchema } from '@graphql-tools/schema'
export default makeExecutableSchema({ typeDefs: ` type Query { hello: String! } `, resolvers: { Query: { hello: () => 'world' } }})That’s the entire setup. Houdini configures the endpoint and wires the client, so don’t set url in your client config or watchSchema in houdini.config.tshoudini.config.js, since Houdini and Vite handle those respectively.
Custom Yoga Instance
To inject context, add plugins, or otherwise customize the server, export a Yoga instance from src/server/+yoga. The +schema file is still required for codegen:
import { createYoga } from 'graphql-yoga'import { schema } from './+schema'import { db } from '../db'
export default createYoga({ schema, context: async ({ request }) => ({ db, user: await getUserFromRequest(request) })})import { createYoga } from 'graphql-yoga'import { schema } from './+schema'import { db } from '../db'
export default createYoga({ schema, context: async ({ request }) => ({ db, user: await getUserFromRequest(request) })})A True BFF
This isn’t just a co-deployed service. During SSR, queries resolve in-process with no network hop. The server renders the page and fulfills the GraphQL requests from the same runtime, which means your resolvers have direct access to the session, your database, and any internal services without exposing them to the outside world.
Security
Because resolvers run exclusively on the server, there’s no risk of leaking sensitive information to the client. API keys, database credentials, and internal service tokens stay in the server environment, and the client only ever receives the fields your queries explicitly ask for. The schema itself never has to be publicly reachable at all.
Configuration
To change the default /_api path the local API is served at, set endpoint in the server-only src/server/+config.tssrc/server/+config.js (typed ServerConfigFile). Codegen bakes it into the client, so you only set it in one place:
import type { ServerConfigFile } from 'houdini'
export default { endpoint: '/_graphql'} satisfies ServerConfigFileUsing a remote API
If your GraphQL API is hosted elsewhere (no local src/server/+schema), point Houdini at it with the top-level url. It ships in the client bundle, so switch it per environment with a VITE_-prefixed variable; the value you pass is the default:
/** @type {import('houdini').ConfigFile} */export default { url: import.meta.env.VITE_API_URL ?? 'http://localhost:4000/graphql'}create-houdini scaffolds this line for you when you start from a remote endpoint. Queries and mutations go directly to that url; @session mutations are routed through Houdini’s server so it can write the session cookie from the result (the value stays server-authoritative). Session signing keys still belong in src/server/+config.tssrc/server/+config.js or the environment, never the public config.