Skip to content

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():

ts
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 ​

CodeLocation
Page datasrc/routes/**/+page.server.ts and +layout.server.ts
Form actionsactions in +page.server.ts
API endpointssrc/routes/**/+server.ts
Server hookssrc/hooks.server.ts
Server-only helperssrc/lib/server/
Holo models, jobs, events, and so onserver/ 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:

ts
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 PageServerLoad

You can return models and collections from load. Holo registers a SvelteKit transport that serializes them for the page.

svelte
<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 ​

ts
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:

ts
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 Actions

For 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:

ts
import { auth } from '@holo-js/auth/sveltekit/server'
import type { LayoutServerLoad } from './$types'

export const load = (async () => {
  return { auth: await auth() }
}) satisfies LayoutServerLoad

auth() 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:

ts
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() and guestOnly() redirect visitors based on whether they are signed in. routes accepts path patterns, regular expressions, or functions. Each option also takes guard and a redirect status.

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:

PackageRoute
authGET /api/auth/user, the current user as JSON for client code
storagefiles 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 ​

ScriptRuns
npm run devholo dev: refreshes generated files, watches server/, starts vite dev
npm run buildholo build: refreshes generated files, runs vite build
npm run startholo start: runs the built server in build/index.js. Use --port or PORT to change the port
npm run typechecksvelte-kit sync && svelte-check

The SvelteKit dev server runs on http://localhost:5173.

Next steps ​

Released under the MIT License.