Skip to Content
About Vovk.ts

Vovk.ts

Back-end Framework for Next.js App Router

GitHub Repo starsRuntime NPM VersionCLI NPM VersionDocs ContextRealtime UI Context

Next.js API layer for SaaS — from MVP to enterprise scale. Multi-tenant routing, third-party-ready APIs with auto-generated docs & type-safe clients, plus MCP-compatible AI tools. All from a single source of truth.

UserController.ts
import { post, prefix, procedure, operation } from "vovk";
import { z } from "zod";

@prefix("users")
export default class UserController {
  @operation({ summary: "Update user" })
  @post("{id}")
  static updateUser = procedure({
    body: z.object({
      email: z.email(),
      profile: z.object({
        name: z.string(), age: z.int() 
      }),
    }),
    params: z.object({ id: z.uuid() }),
  }).handle(async (req, { id }) => {
    return UserService.updateUser(id);
  });
}
MCP Server
server.registerTool(name,
  { title, inputSchema },
  execute);
__init__.py
def update_user(
  body: UpdateUserBody,
) -> UpdateUserOutput:
page.tsx
// SSR: no HTTP round-trip
const res = await
  UserController.updateUser
  .fn({ body, params });
Cargo.toml
[package]
name = "vovk_hello_world"
edition = "2021"
README (TS)
## UserRPC.updateUser
> Update user by ID
await UserRPC.updateUser({
  body, query, params });
openapi.json
"/api/users/{id}": {
  "post": {
    "summary": "Update user" }}
lib.rs
pub async fn update_user(
  body: update_user_::body,
) -> Result<output>
client.ts
// RPC call over HTTP
const res = await
  UserRPC.updateUser(
  { body, query, params });
pyproject.toml
[project]
name = "vovk_hello_world"
dependencies = ["requests"]
AI Tools
const { tools } =
  deriveTools({
    modules:
      { UserRPC } });
README (RS)
## user_rpc::update_user
> Update user by ID
user_rpc::update_user(
  body, query, params)
package.json
"name": "vovk-hello-world",
"main": "./index.js",
"types": "./index.d.ts"
UserService.ts
body: VovkBody<
  typeof UserController
  .updateUser>
README (PY)
## UserRPC.update_user
> Update user by ID
UserRPC.update_user(
  body=body, params=params)

Vovk.ts adds a structured API layer on top of Next.js App Router Route Handlers. The unit is the procedure — a typed function paired with its schema. From that single source, Vovk derives the HTTP endpoint, the local .fn() call, the typed RPC client, the OpenAPI document, and the AI tool with execute. No separate contract layer, no glue code.

Run init command in an existing Next.js project to get started.

npx vovk-cli@latest init

Requires Node.js 22+ and Next.js 15+.   Quick Start · Manual Install · Claude Plugin · GitHub 


What it looks like

A procedure is a typed, validated callable. Define inputs and output with procedure — params, query, body — and call it directly on the server for 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: '123' } });

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

export default class UserService { static async getUser(id: VovkParams<typeof UserController.getUser>['id']) { return prisma.user.findUnique({ where: { id } }); } }

Add an HTTP decorator and the same procedure becomes a Next.js Route Handler — call shape unchanged:

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); }); }

Codegen reads the emitted schema and produces a fetch-powered client that mirrors the .fn() signature:

import { UserRPC } from 'vovk-client'; const user = await UserRPC.getUser({ params: { id: '123' } });

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); }

Annotate with @operation and the same procedure exposes as an LLM tool — pass controllers (in-process) or RPC modules (HTTP) to deriveTools:

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

What one procedure becomes

Function plus schema is a complete unit. From that pair Vovk derives:

  • the Next.js Route Handler — add an HTTP decorator and the same procedure mounts as an endpoint
  • the local .fn() callable — same call shape as the RPC client; use it in SSR, server components, and server actions
  • the typed RPC client modulefetch-powered, generated from the emitted schema
  • the OpenAPI 3.x document — derived from the same schema, no parallel spec to maintain
  • the LLM tool with name, description, parameters, and execute — via deriveTools
  • a generated README.md — client library documentation rendered from the procedure surface

One source, multiple destinations. The sections below cover each in detail.


How it works

Segments

Every group of controllers lives in a segment — a Next.js catch-all route that compiles into its own serverless function. Segments are configured independently.

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

Schema emission

Handlers are the source of truth. Vovk.ts derives schema from the code you already wrote and emits it as a build artifact to .vovk-schema/. The runtime stays lean; tooling reads the schema.

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

Generated TypeScript clients

Controllers compile into RPC modules with a consistent { params, query, body } call signature. Generate a single composed client or per-segment clients. See TypeScript Client.

Direct type mapping between server and client code gives you jump-to-definition and JSDoc on hover over generated RPC methods.

Validation

Vovk.ts works with any library that implements Standard Schema  + Standard JSON Schema  — including Zod, Valibot, and ArkType.

OpenAPI mixins

Third-party OpenAPI 3.x schemas can be converted into modules that share the same calling convention as your own endpoints — then used through the same client and tooling pipeline:

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

See OpenAPI Mixins.

AI tool derivation

Annotate methods with @operation, then derive tool definitions for LLM function calling — from controllers (same-context), RPC modules (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 'vovk-client'; const { tools } = deriveTools({ modules: { TaskRPC, PetstoreAPI } }); // [{ name, description, parameters, execute }, ...]

Each tool has name, description, parameters (JSON Schema), and an execute function. See Deriving AI Tools.

Streaming

Procedures can yield JSON Lines for real-time responses. See JSON Lines.

Local procedure calls

Procedures call directly on the server with .fn() — same call shape as the generated HTTP client. Use them in SSR/PPR, server components, and server actions. See Calling Procedures Locally.

Docs and publishing

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

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


Packages

PackageRoleVersionInstall
vovkRuntime — decorators, procedure, routing, deriveToolsNPM Versionproduction
vovk-cliToolchain — codegen, mixins, docs, bundlingNPM Versiondev
vovk-clientRe-exported composed clientNPM Versionproduction (optional)
vovk-ajvClient-side validation with AJVNPM Versionproduction (optional)
vovk-pythonPython client generation (experimental)NPM Versiondev (optional)
vovk-rustRust client generation (experimental)NPM Versiondev (optional)

See Packages.


Claude Plugin

The official Claude Code plugin ships 15 topic-based skills that teach the coding agent how to use Vovk.ts when you describe what you want to build. Skills load only when relevant — typing “scaffold a tenant” loads the multitenant skill, “stream chat tokens” loads JSON Lines.

Install (inside Claude Code):

/plugin marketplace add finom/vovk /plugin install vovk@vovk /reload-plugins

See Claude Plugin for the full skill list, the AI mind model behind the framework, and why the pairing works.


Examples

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

The Multitenancy Tutorial walks through hosting multiple tenants from different subdomains within a single Next.js app.

The Realtime UI tutorial series builds a full-stack Kanban board where users, bots, AI agents, and MCP clients update the board in real time — covering state normalization, database polling, AI chat, voice AI, and Telegram integration.

Browse more snippets on the Random Examples  site.


Vocabulary

TermMeaning
ControllerClass that groups procedures as static members; HTTP decorators expose them as endpoints
ProcedureA typed, validated callable created with procedure. Call it locally with .fn(), expose it over HTTP with a decorator, or derive it as an LLM tool
SegmentA routed back-end slice, compiled independently into its own function
RPC moduleGenerated client module mirroring a controller
API moduleGenerated module from a controller or an OpenAPI schema
Last updated on