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.
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.
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:
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.
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.
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.
pnpm vite build
node dist/server.js
Send one request from another 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:
{"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:
- H3 receives the HTTP request and parses its JSON body.
- The
kvRuntime Helper reads and writes application state. 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.
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.