Using Holo with SvelteKit
Holo runs inside your SvelteKit 3 project. You write load functions, form actions, and +server.ts endpoints as usual, and call Holo models and services from them.
This page explains how Holo plugs into SvelteKit and where your code goes. It assumes a project created with npm create holo-js@latest -- --framework sveltekit.
How Holo plugs in
SvelteKit 3 reads its options from the sveltekit() plugin in vite.config.ts. Holo wraps those options with withHoloSvelteKit():
import adapter from '@sveltejs/adapter-node'
import { withHoloSvelteKit } from '@holo-js/adapter-sveltekit/config'
import { sveltekit } from '@sveltejs/kit/vite'
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
sveltekit(withHoloSvelteKit({
adapter: adapter(),
preprocess: vitePreprocess(),
})),
],
})The wrapper points SvelteKit's hook files at Holo-generated hooks in .holo-js/generated/. Those hooks start the Holo runtime, run each request with its own session and user, handle the routes Holo adds, and then call your own hooks from src/hooks.server.ts and src/hooks.ts.
Edit the object inside withHoloSvelteKit() like the options you pass to sveltekit(). Leave the wrapper in place.
SvelteKit 3 no longer reads svelte.config.js. If your project still has one, move its options into the sveltekit() call and delete the file. holo prepare stops with an error until you do.
WARNING
Don't set files.hooks yourself. withHoloSvelteKit() throws an error if you point the hooks somewhere else. Put your hook code in src/hooks.server.ts and src/hooks.ts instead.
New projects use @sveltejs/adapter-node. vite.config.ts also lists Holo's packages under ssr.external so Vite loads them from node_modules on the server.
Where server code goes
| Code | Location |
|---|---|
| Page data | src/routes/**/+page.server.ts and +layout.server.ts |
| Form actions | actions in +page.server.ts |
| API endpoints | src/routes/**/+server.ts |
| Server hooks | src/hooks.server.ts |
| Server-only helpers | src/lib/server/ |
| Holo models, jobs, events, and so on | server/ at the project root |
The scaffold's tsconfig.json is the one sv create writes, with server, config, and .holo-js/generated/**/*.ts added to include so svelte-check also checks your Holo code.
Use Holo in load functions
Import models and query them in a server load function:
import Post from '../../../server/models/Post'
import type { PageServerLoad } from './$types'
export const load = (async () => {
const posts = await Post
.with('category')
.where('status', 'published')
.latest('published_at')
.get()
return { posts }
}) satisfies PageServerLoadYou can return models and collections from load. Holo registers a SvelteKit transport that serializes them for the page.
<script lang="ts">
let { data } = $props()
</script>
<ul>
{#each data.posts as post (post.id)}
<li>{post.title}</li>
{/each}
</ul>TIP
Keep Holo calls in server-only files: +page.server.ts, +layout.server.ts, +server.ts, hooks, and src/lib/server. A universal +page.ts also runs in the browser, where Holo is not available.
Use Holo in endpoints
import Post from '../../../../server/models/Post'
export async function GET() {
const posts = await Post.where('status', 'published').latest().get()
return Response.json({ posts })
}If an endpoint or action throws a validation or rate-limit error, Holo's hooks turn it into the matching error response. See Error Responses.
Form actions and redirects
Use SvelteKit's own redirect() in actions:
import { redirect } from '@sveltejs/kit'
import Post from '../../../../server/models/Post'
import type { Actions } from './$types'
export const actions = {
default: async ({ request }) => {
const formData = await request.formData()
const post = await Post.create({
title: String(formData.get('title')),
body: String(formData.get('body')),
})
redirect(303, `/posts/${post.id}`)
},
} satisfies ActionsFor real forms, validate the input first with Forms. The Framework Recipes page shows a complete SvelteKit form.
Read the current user
With the auth package installed, call auth() from @holo-js/auth/sveltekit/server in any server load, action, or endpoint:
import { auth } from '@holo-js/auth/sveltekit/server'
import type { LayoutServerLoad } from './$types'
export const load = (async () => {
return { auth: await auth() }
}) satisfies LayoutServerLoadauth() returns { authenticated, guard, provider, user }. Pass { guard: 'admin' } to check a different guard. See Authentication.
Your own hooks
Export handle, handleError, or handleFetch from src/hooks.server.ts. Holo runs its own handling first, then yours. Use SvelteKit's sequence() to combine several handlers:
import { sequence } from '@sveltejs/kit/hooks'
import { authOnly, guestOnly } from '@holo-js/auth/sveltekit/server'
import { csrfProtection } from '@holo-js/security/sveltekit/server'
export const handle = sequence(
csrfProtection(),
guestOnly({
routes: ['/login', '/register'],
redirectTo: '/admin',
}),
authOnly({
routes: ['/admin/*'],
redirectTo: '/login',
}),
)csrfProtection()rejects form posts without a valid CSRF token. See CSRF Protection.authOnly()andguestOnly()redirect visitors based on whether they are signed in.routesaccepts path patterns, regular expressions, or functions. Each option also takesguardand a redirectstatus.
Without your own handleError, unexpected errors are logged and the response carries SvelteKit's generic message, unless debug is true in config/app.ts. See Unexpected errors.
In src/hooks.ts you can export reroute and transport. Holo merges them with its own.
Authorization errors
When authorize() from Authorization denies access, Holo throws a SvelteKit error() with status 403 or 404, so your +error.svelte page renders. See Denied Responses.
Routes Holo adds
The Holo hooks answer a few routes directly, depending on which packages you installed:
| Package | Route |
|---|---|
auth | GET /api/auth/user, the current user as JSON for client code |
storage | files from the public disk under /storage |
broadcast | /broadcasting/config and /broadcasting/auth |
realtime | /holo/realtime/query and /holo/realtime/mutation |
Client helpers
Client-side helpers come from @holo-js/adapter-sveltekit/client, such as useForm() for forms with typed validation errors. Pass pageData: data from the page's data prop, which comes from the root layout load that returns readValidationFlash(), so flashed errors and input are restored. See Client-Side Forms, Framework Recipes, and Current User on the Client.
Scripts
| Script | Runs |
|---|---|
npm run dev | holo dev: refreshes generated files, watches server/, starts vite dev |
npm run build | holo build: refreshes generated files, runs vite build |
npm run start | holo start: runs the built server in build/index.js. Use --port or PORT to change the port |
npm run typecheck | svelte-kit sync && svelte-check |
The SvelteKit dev server runs on http://localhost:5173.