“Hello World” Example
About this example: Although it is called “Hello World,” this sample is intentionally comprehensive. It brings together validation, streaming, multi‑language client generation, and OpenAPI output in a single, focused project. If you need the absolute minimal starting point, see the Quick Start guide first.
If you want a more advanced, agent-operated reference app (Realtime UI + MCP + voice/chat surfaces), see the Realtime UI overview.
The “Hello World” app at hello-world.vovk.dev is a Next.js / Vovk.ts example showcasing core features by implementing:
- Back-end:
UserControllerwith anupdateUsermethod (POST/api/users/{id}).StreamControllerwith a JSONLinesstreamTokenshandler (GET/api/streams/tokens).OpenApiControllerwithgetSpec(GET/api/static/openapi/spec.json), serving the generated OpenAPI spec. Documentation is viewable at the/openapipage.
- Front-end:
- A form plus a JSONLines streaming demo above it.
- Configuration:
- Client‑side validation; segmented & composed TypeScript clients; generation of Rust/Python clients; npm bundle output; and OpenAPI metadata (info + servers).
Explore the source in the GitHub repository . Generated artifacts are committed under dist , tmp_prebundle , dist_rust , and dist_python for inspection (in real projects they should go in .gitignore).
All snippets on this page are pulled directly from GitHub; live example pages are embedded via iframes.
Running the Example Locally
Clone and install:
git clone https://github.com/finom/vovk-hello-world.git
cd vovk-hello-world
npm iStart the dev server:
npm run devThen open http://localhost:3000 .
Topics and Concepts Covered
- Zod validation via procedure function for
body,query, andparamsinput, plusoutputanditerationoutput schemas withdescriptionandexamplesvia Zod meta . - Client-side validation for RPC inputs, described in the customization article.
- Composed and segmented TypeScript clients.
- JSONLines streaming.
- Type inference between service and controller layers.
useQuery/useMutationusage withqueryKey.- TypeScript client bundle published on npm .
- Experimental Rust and Python clients published on crates.io / PyPI .
- OpenAPI spec served from a static segment and rendered via Scalar .
Live Demo
The demo provides a simple form (no native validation attributes) and a “Disable client-side input validation” checkbox toggling the disableClientValidation option. “Notification type” is intentionally mis-set to show both client and server validation behavior.
UserController and UserService
/api/users/{id} is implemented by UserController / UserService through updateUser.
The controller method uses @post to map POST and procedure() for Zod validation of body, params, query and output. Each schema applies meta for richer OpenAPI (description, examples). For illustration, body nests email and a profile object (name, age).
The service method infers parameter types from the procedure, returning the service method result directly and avoiding implicit any self-reference issues.
UserController.ts
import { z } from "zod";
import { procedure, prefix, post, operation } from "vovk";
import UserService from "./UserService";
@prefix("users")
export default class UserController {
@operation({
summary: "Update user",
description: "Update user by ID",
})
@post("{id}")
static updateUser = procedure({
body: z
.object({
email: z.email().meta({
description: "User email",
examples: ["john@example.com", "jane@example.com"],
}),
profile: z
.object({
name: z
.string()
.min(2)
.meta({
description: "User full name",
examples: ["John Doe", "Jane Smith"],
}),
age: z
.int()
.min(16)
.max(120)
.meta({ description: "User age", examples: [25, 30] }),
})
.meta({ description: "User profile object" }),
})
.meta({ description: "User data object" }),
params: z
.object({
id: z.uuid().meta({
description: "User ID",
examples: ["123e4567-e89b-12d3-a456-426614174000"],
}),
})
.meta({
description: "Path parameters",
}),
query: z
.object({
notify: z
.enum(["email", "push", "none"])
.meta({ description: "Notification type" }),
})
.meta({
description: "Query parameters",
}),
output: z
.object({
success: z.boolean().meta({ description: "Success status" }),
id: z.uuid().meta({ description: "User ID" }),
notify: z.enum(["email", "push", "none"]).meta({
description: "Notification type",
}),
})
.meta({ description: "Response object" }),
async handle(req, { id }) {
const body = await req.json();
const notify = req.nextUrl.searchParams.get("notify");
return UserService.updateUser(id, body, notify);
},
});
}StreamController and StreamService
/api/streams/tokens streams tokens using a controller generator method that delegates with yield* to the service. Each streamed item is validated via the iteration schema. Delays are simulated with setTimeout.
StreamController.ts
import { procedure, prefix, get, operation } from "vovk";
import { z } from "zod";
import StreamService from "./StreamService";
@prefix("streams")
export default class StreamController {
@operation({
summary: "Stream tokens",
description: "Stream tokens to the client",
})
@get("tokens")
static streamTokens = procedure({
iteration: z
.object({
message: z.string().meta({ description: "Message from the token" }),
})
.meta({
description: "Streamed token object",
}),
async *handle() {
yield* StreamService.streamTokens();
},
});
}React Components
The demo uses @tanstack/react-query for both standard requests and streaming.
index.tsx
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import StreamDemo from "./StreamDemo";
import UserFormDemo from "./UserFormDemo";
const queryClient = new QueryClient();
const Demo = () => {
return (
<QueryClientProvider client={queryClient}>
<StreamDemo />
<h2 className="text-lg font-bold mb-1 text-center">
"Update User" Demo
</h2>
<p className="text-xs mb-4 text-center">
<strong>*</strong> form validation isn't enabled for demo purposes
</p>
<UserFormDemo />
</QueryClientProvider>
);
};
export default Demo;Config
The app is configured to:
- Client-side validation via Ajv, the primary client-side validation library, described in the customization article.
- Generate Rust and Python code when running vovk dev or vovk generate.
- Demonstrate the segmented client, generating RPC modules per segment.
- Generate clients and bundle with an explicit
origin:- Python and Rust clients: use
https://hello-world.vovk.dev. - TypeScript Bundle: use
https://hello-world.vovk.dev. - Composed TypeScript client:
- In development: use
http://localhost:PORT(so Node.js via vovk-client can call locally). - In production: use an empty origin (requests are relative to the current origin).
- In development: use
- Segmented client: use an empty origin (requests are relative to the current origin).
- Python and Rust clients: use
// @ts-check
const PROD_ORIGIN = "https://hello-world.vovk.dev";
// Commented lines indicate default values
/** @type {import('vovk').VovkConfig} */
const config = {
logLevel: "debug",
outputConfig: {
imports: {
validateOnClient: "vovk-ajv",
},
openAPIObject: {
info: {
title: '"Hello World" app API',
description:
'API for "Hello World" app hosted at https://hello-world.vovk.dev/. Source code is available on Github https://github.com/finom/vovk-hello-world. For more information about this app, visit the documentation page https://vovk.dev/hello-world.',
license: {
name: "MIT",
url: "https://opensource.org/licenses/MIT",
},
version: "1.0.0",
},
servers: [
{
url: "https://hello-world.vovk.dev",
description: "Production",
},
{
url: "http://localhost:3000",
description: "Localhost",
},
],
},
},
composedClient: {
fromTemplates: ["mjs", "cjs", "py", "rs"],
// enabled: true,
// outDir: "./node_modules/.vovk-client",
outputConfig: {
origin:
process.env.NODE_ENV === "production"
? null
: `http://localhost:${process.env.PORT ?? 3000}`,
},
// prettifyClient: false,
},
segmentedClient: {
// fromTemplates: ["ts"],
enabled: true,
// outDir: "./src/client",
// outputConfig: { origin: '' },
// prettifyClient: true,
},
bundle: {
outputConfig: { origin: PROD_ORIGIN },
keepPrebundleDir: true,
build: async ({ entry, outDir }) => {
const { build } = await import("tsdown");
await build({
entry,
dts: true,
format: ["cjs", "esm"],
hash: false,
fixedExtension: true,
clean: true,
outDir,
tsconfig: "./tsconfig.bundle.json",
});
},
},
clientTemplateDefs: {
py: {
extends: "py",
outputConfig: { origin: PROD_ORIGIN },
// composedClient: { outDir: "./dist_python" },
},
rs: {
extends: "rs",
outputConfig: { origin: PROD_ORIGIN },
// composedClient: { outDir: "./dist_rust" },
},
},
};
module.exports = config;The code above is fetched from GitHub repository.
OpenAPI Specification
The OpenAPI specification is served by a GET endpoint returning the generated spec (openapi from vovk-client/openapi).
OpenApiController.ts
import { get, operation } from "vovk";
import { openapi } from "vovk-client/openapi";
export default class OpenApiController {
@operation({
summary: "OpenAPI spec",
description: 'Get the OpenAPI spec for the "Hello World" app API',
})
@get("openapi.json", { cors: true })
static getSpec = () => openapi;
}The spec includes Scalar‑compatible samples you can reuse immediately.
Building and Packaging
This example also demonstrates how to quickly produce distributable packages published on npm , PyPI , and crates.io . The provided templates compile ready-to-use packages with language-specific files such as package.json , Cargo.toml , and pyproject.toml , as well as README files that use code samples served as API/client documentation.
npm run patch:
- Verifies a clean working tree.
- Bumps the patch version.
- Triggers
postversionto regenerate clients, bundle TypeScript, publish all packages, and create a commit + tag.
"scripts": {
// ...
"publish:node": "npm publish ./dist",
"publish:rust": "cargo publish --manifest-path dist_rust/Cargo.toml --allow-dirty",
"publish:python": "python3 -m build ./dist_python --wheel --sdist && python3 -m twine upload ./dist_python/dist/*",
"git-tag": "git add . && git commit -m \"chore: release v$(node -p \"require('./package.json').version\")\" && git tag v$(node -p \"require('./package.json').version\")",
"check-uncommitted": "git diff --quiet && git diff --cached --quiet || (echo '❌ Uncommitted changes!' && exit 1)",
"postversion": "vovk generate && vovk bundle && npm run publish:node && npm run publish:rust && npm run publish:python && npm run git-tag",
"patch": "npm run check-uncommitted && npm version patch --no-git-tag-version"
}The README.md files are also updated with the latest examples and descriptions. IFrames below are rendered from the READMEs by Github Pages.
Tests
The project also includes tests located in the test/node , test/rust , and test/python directories, covering both local and published clients.
npm run testWhen run, the command builds the Next.js app, starts the server, and executes the tests.
Conclusion
Even with only three endpoints (updateUser, streamTokens, getSpec), Vovk.ts delivers:
- Turnkey multi-language client generation (TypeScript bundle + Rust + Python) with publish-ready metadata and READMEs.
- Rich, documented OpenAPI output rendered via Scalar.
- Text streaming and unified client-side validation flows.