Tutorials

Build your first Server Primitive

Add a persistent KV store to a small Vite server and prove the result with one request.
maxi

maxi

@onmax

Server Primitives give application code a stable API for infrastructure such as KV, Blob, Database, Queue, Workflow, Schedule, Sandbox, and Workspace. You select the provider at the Vite Integration boundary instead of spreading a provider SDK through product code.

This tutorial adds KV to a small H3 server. One request writes a value, reads it back, and returns the stored result. The first proof stays local and requires no account or credential.

You need Node.js 24.15 or newer and pnpm. The tutorial uses H3 as the HTTP server and Node.js as the host so every runtime boundary remains visible.

Create the project

Create an empty directory and install ViteHub with the small server used in this tutorial.

Terminal
mkdir vitehub-kv-start
cd vitehub-kv-start
pnpm init
pnpm pkg set type=module
pnpm add vite-hub h3 vite

Your project ends with two source files:

Project
vitehub-kv-start/
├── src/
│   └── server.ts
└── vite.config.ts

Select the local KV store

Register vitehub() in Vite and select the fs-lite driver explicitly. ViteHub stores local values under .vitehub/data/kv, while server code only sees the kv Runtime Helper.

vite.config.ts
import { resolve } from "node:path"

import { defineConfig } from "vite"
import { vitehub } from "vite-hub"

export default defineConfig({
  root: import.meta.dirname,
  appType: "custom",
  build: {
    outDir: "dist",
    rollupOptions: {
      input: resolve(import.meta.dirname, "src/server.ts"),
      output: { entryFileNames: "server.js" },
    },
    ssr: true,
  },
  plugins: [
    vitehub({
      preset: "node",
      blob: false,
      env: false,
      kv: { driver: "fs-lite", base: ".vitehub/data/kv" },
    }),
  ],
})

The Vite build compiles one Node.js server entry. appType: "custom" tells Vite that H3, rather than an HTML page, owns the application response.

Write and read one value

Create one H3 route. The handler writes the request body to settings, then reads the same key before returning.

src/server.ts
import { createServer } from "node:http"

import { H3, readBody } from "h3"
import { toNodeHandler } from "h3/node"
import { kv } from "vite-hub/kv"

const app = new H3().post("/settings", async (event) => {
  const settings = await readBody<{ theme: string }>(event)

  const [writeError] = await kv.set("settings", settings)
  if (writeError) throw writeError

  const [readError, storedSettings] = await kv.get("settings")
  if (readError) throw readError
  return { settings: storedSettings }
})

const port = Number(process.env.PORT || 5173)

createServer(toNodeHandler(app)).listen(port, () => {
  console.log(`ViteHub KV tutorial listening on http://localhost:${port}`)
})

H3 owns HTTP parsing and Node.js owns the listening socket. ViteHub owns the KV configuration and the stable storage API inside the route.

Run the proof

Build the server, then start the generated entry.

Terminal
pnpm vite build
node dist/server.js

Send one request from another terminal.

Terminal
curl -X POST http://localhost:5173/settings \
  -H 'content-type: application/json' \
  -d '{"theme":"system"}'

The server returns the value it read through ViteHub:

Response
{"settings":{"theme":"system"}}

The value survives a server restart because the selected driver writes to .vitehub/data/kv. Delete that directory whenever you want to reset the local store.

What you built

The request crosses three explicit boundaries:

  1. H3 receives the HTTP request and parses its JSON body.
  2. The kv Runtime Helper reads and writes application state.
  3. vitehub() resolves the concrete store during the Vite build.

Changing providers belongs in vite.config.ts. The route keeps importing kv from vite-hub/kv when you move to a supported hosted store.

Keep the local driver while you shape the feature. Choose a hosted driver only when you know the deployment target and its persistence requirements.

Continue from here

  • Read KV for named stores and hosted drivers.
  • Read Runtime Helpers and stable imports to see how provider changes stay out of server code.
  • Build your first Agent when the product needs an actor that can invoke models, coding providers, or application-owned logic.