Skip to Content
About Vovk.ts

About Vovk.ts

Vovk.ts adds an API layer on top of Next.js App Router Route Handlers. Its unit is the procedure: a typed function with its schema. From one procedure, Vovk.ts derives the HTTP endpoint, the local .fn() call, the typed RPC client, the OpenAPI document and the AI tool with execute. You don’t write a separate contract or glue code.

To start, run the init command in an existing Next.js project.

npx vovk-cli@latest init

Requires Node.js 24.15+, Next.js 15+ and TypeScript 5.5+.

Quick Start · Manual Install · Agent Skills · GitHub 


What it looks like

A procedure is a typed, validated function. Define its params, query, body and output with procedure, and call it on the server in SSR, server components or server actions:

export default class UserController { static getUser = procedure({ params: z.object({ id: z.string().uuid() }), output: z.object({ id: z.string(), name: z.string() }), }).handle(async (req, { id }) => { return UserService.getUser(id); }); }
const user = await UserController.getUser.fn({ params: { id: '123e4567-e89b-12d3-a456-426614174000' } });

Services hold the business logic. Plain classes, no decorators:

export default class UserService { static async getUser(id: VovkParams<typeof UserController.getUser>['id']) { const user = await prisma.user.findUnique({ where: { id } }); if (!user) throw new HttpException(HttpStatus.NOT_FOUND, 'User not found'); return user; } }

Add an HTTP decorator, and the same procedure is also a Next.js Route Handler, with the same call shape:

export default class UserController { @get('{id}') static getUser = procedure({ params: z.object({ id: z.string().uuid() }), output: z.object({ id: z.string(), name: z.string() }), }).handle(async (req, { id }) => { return UserService.getUser(id); }); }

The CLI reads the emitted schema and generates a fetch-based client with the .fn() signature:

import { UserRPC } from '@/client'; const user = await UserRPC.getUser({ params: { id: '123e4567-e89b-12d3-a456-426614174000' } });

Procedures can yield JSON Lines for real-time streaming:

export default class StreamController { @post('completions') static streamTokens = procedure({ iteration: z.object({ message: z.string() }), }).handle(async function* () { yield* StreamService.getTokens(); }); }
using stream = await StreamRPC.streamTokens(); for await (const { message } of stream) { console.log(message); }

Add @operation, and the same procedure is also an LLM tool. Pass controllers (in-process) or RPC modules (over HTTP) to deriveTools:

const tools = deriveTools({ modules: { UserRPC, TaskController } }); // [{ name, description, inputSchema, execute, ... }, ...]

What one procedure becomes

From one function and its schema, Vovk.ts derives:

  • the Next.js Route Handler: add an HTTP decorator to serve the procedure as an endpoint
  • the local .fn() call, with the same call shape as the RPC client, for SSR, server components and server actions
  • the typed RPC client module, generated from the emitted schema, using fetch
  • the OpenAPI 3.x document, from the same schema, so you don’t maintain a separate spec
  • the LLM tool with name, description, inputSchema and execute, from deriveTools
  • a generated README.md that documents the client library

How it works

Segments

Controllers live in a segment: a Next.js catch-all route that compiles into its own serverless function. Each segment has its own configuration.

src/app/api/[[...vovk]]/route.ts
const controllers = { UserRPC: UserController }; export type Controllers = typeof controllers; export const { GET, POST, PATCH, PUT, HEAD, OPTIONS, DELETE } = initSegment({ controllers });

Schema emission

Handlers are the source of truth. Vovk.ts derives the schema from your code and writes it to .vovk-schema/ as a build artifact. The tools read the schema; the server runtime doesn’t.

    • root.json
    • customer.json
      • foo.json
    • _meta.json

Generated TypeScript clients

Controllers compile into RPC modules that all take { params, query, body }. Generate one composed client or per-segment clients. See TypeScript Client.

Client types map directly to server code, so jump-to-definition and JSDoc on hover work on generated RPC methods.

Validation

Vovk.ts works with any library that implements Standard Schema  and Standard JSON Schema , such as Zod, Valibot and ArkType.

OpenAPI mixins

Vovk.ts converts third-party OpenAPI 3.x schemas into modules with the same call shape as your own endpoints. You use them through the same client and tools:

import { PetstoreAPI } from '@/client'; const pet = await PetstoreAPI.getPetById({ params: { petId: 1 } });

See OpenAPI Mixins.

AI tool derivation

Add @operation to methods, then derive tools for LLM function calling from controllers (in-process), RPC modules (over HTTP) or third-party APIs:

export default class TaskController { @operation({ summary: 'Create task', description: 'Creates a new task.' }) @post() static createTask = procedure({ body: z.object({ title: z.string() }), output: z.object({ id: z.string(), title: z.string() }), }).handle(async (req) => { // ... }); }
import { deriveTools } from 'vovk'; import { TaskRPC, PetstoreAPI } from '@/client'; const tools = deriveTools({ modules: { TaskRPC, PetstoreAPI } }); // [{ name, description, inputSchema, execute, ... }, ...]

Each tool has name, description, inputSchema (a Standard Schema that also gives JSON Schema) and an execute function. See Deriving AI Tools.

Streaming

See JSON Lines for generator handlers, the client’s async iterator and JSONLinesResponder.

Local procedure calls

Call a procedure on the server with .fn(). It takes the same arguments as the generated HTTP client. Use it in SSR/PPR, server components and server actions. See Calling Procedures Locally.

Docs and publishing

Generate OpenAPI 3.x documentation, and package TypeScript, Python or Rust client libraries for publishing.

See Generate Command · Bundle Command · Python Client · Rust Client


Packages

PackageRoleVersionInstall
vovkRuntime: decorators, procedure, routing, deriveToolsNPM Versionproduction
vovk-cliCLI: codegen, mixins, docs, bundlingNPM Versiondev
vovk-ajvClient-side validation with AJVNPM Versionproduction (optional)
vovk-pythonPython client generationNPM Versiondev (optional)
vovk-rustRust client generationNPM Versiondev (optional)

See Packages.


Agent Skills

15 topic skills teach a coding agent Vovk.ts as you describe what to build: Claude Code, Cursor, Windsurf, GitHub Copilot, Cline and the other agents that read skills. A skill loads only when needed: “scaffold a tenant” loads the multitenant skill, “stream chat tokens” the JSON Lines one.

Install them in your project:

npx skills add finom/vovk

Quick Start has the prompts to try, the per-agent install commands and the Claude Code plugin.

The skills

With npx skills, they carry the names below without the vovk: prefix.

  • vovk:init — set up Vovk.ts in a Next.js App Router project, or create a new Next.js app and run vovk init in it.
  • vovk:base — the essentials: what vovk exports, the project layout, a complete endpoint, how requests, validation and errors behave, status codes, a custom error format, CORS, and how to get the schema, the typed client and the OpenAPI document.
  • vovk:config — the vovk.config.{mjs,cjs,js} shape, every config key and default (rootEntry, schemaOutDir, composedClient, segmentedClient, clientTemplateDefs, outputConfig, bundle, …), and the tsconfig.json setup.
  • vovk:segment — segments (root, named, static), initSegment, segment priority, generateStaticParams.
  • vovk:multitenant — routing tenants by subdomain: the multitenant() proxy, the overrides shape, per-tenant segments and front-end pages, wildcard DNS.
  • vovk:procedure — procedures, validation (Zod / Valibot / ArkType), controllers, HTTP decorators, req.vovk, error handling, content types, .fn() for SSR / server components / server actions.
  • vovk:decorators — built-in and custom decorators (createDecorator), authorization patterns, req.vovk.meta(), stacking order.
  • vovk:rpc — the generated RPC client (@/client), composed vs segmented clients, call shape, createFetcher, error rethrow, type inference from client methods.
  • vovk:jsonlines — JSON Lines streaming: generator handlers, JSONLinesResponder, progressive(), client async iteration, using, asPromise, abort.
  • vovk:openapi — OpenAPI 3.x generation: @operation metadata, outputConfig.openAPIObject, per-segment overrides, serving the spec, Scalar docs.
  • vovk:mixins — import third-party OpenAPI 3.x schemas as typed client modules, called like your own RPC modules.
  • vovk:tools — procedures as LLM tools with deriveTools(), MCP-compatible output, @operation, controllers vs RPC modules, OpenAI / Anthropic / MCP wiring.
  • vovk:bundle — the vovk bundle command for publishable TypeScript SDKs.
  • vovk:python — generate a typed Python client (vovk-python), py / pySrc templates, TypedDict shapes, JSON Lines via Python generators, PyPI publishing.
  • vovk:rust — generate a typed Rust crate (vovk-rust), rs / rsSrc templates, async reqwest call shape, reading a futures::Stream, crates.io publishing.

What the agent gets from Vovk.ts

Vovk.ts sets where code goes, so the agent can predict it:

  • Code by feature in src/modules/<name>/: a controller and a service per feature, not files spread across lib/.
  • Controller and service apart: controllers define the decorated procedures; services, plain classes, hold the business logic. What the endpoint is stays apart from what it does.
  • Service methods, not loose helpers: fewer files and a predictable layout.
  • One source of truth: the same procedure().handle() serves the HTTP endpoint, the SSR call (.fn()) and the AI tool (deriveTools). Nothing is duplicated, so the model has less to reconcile.
  • Plain REST: curl and fetch work. Types go end to end without a custom protocol.
  • Multitenancy: the multitenant() proxy and a segment per tenant host many tenants on subdomains in one Next.js app.
  • OpenAPI and AI tools: the schema comes from the procedures, Scalar shows the generated code samples, and one line makes a procedure an LLM tool.

Why skills

Without them, asking an agent to “scaffold a Vovk procedure with Zod validation” goes one of two ways. The model makes up an API from months-old training data, though the Vovk.ts API has changed. Or it fetches vovk.dev mid-task, which is slow, hits rate limits and often loads the wrong page first.

With the skills, the agent loads the framework’s patterns by topic:

  • One topic at a time. Only the needed skills load, so the agent doesn’t read thousands of lines of docs for one question.
  • Self-contained. The base skill holds the facts a typical API build needs, checked against vovk 4’s code. Its description asks the agent to load it first for any Vovk.ts task; the agent decides, and on a change to an existing project it may load another skill first. It tells the agent not to fetch vovk.dev mid-task and not to read vovk’s source in node_modules: the skills are the source of truth. They work offline, with a predictable cost and no rate limits.
  • Handoffs between skills. A skill points to the one that owns a topic: mixins to tools for LLM tools, procedure to jsonlines for streaming. The agent loads the context it needs, not the context next to it.
  • Compressed text. The topic skills use terse “caveman” prose, with the same content: articles and filler dropped, code blocks unchanged.

Examples

The “Hello World” example shows Vovk.ts end to end in one project: Zod-validated endpoints, JSON Lines streaming, composed and segmented clients, OpenAPI docs with Scalar, and bundled client libraries in TypeScript, Python and Rust.

The Multitenancy Tutorial shows how to serve several tenants from different subdomains in one Next.js app.

The Realtime Kanban example builds a board that users, bots, AI agents and MCP clients update in real time. It covers state normalization, database polling, AI chat, voice AI and Telegram.

More snippets are on the Random Examples  site.


Vocabulary

TermMeaning
ControllerA class that groups procedures as static members; HTTP decorators serve them as endpoints
ProcedureA typed, validated function made with procedure. Call it locally with .fn(), serve it over HTTP with a decorator, or derive an LLM tool from it
SegmentA part of the back end with its own route and function
RPC moduleGenerated client module that mirrors a controller
API moduleGenerated module from a controller or an OpenAPI schema
Last updated on