Skip to Content
About Vovk.ts

Vovk.ts

Back-end Framework for Next.js App Router

GitHub Repo starsRuntime NPM VersionCLI NPM VersionDocs Context

Back-end Framework for Next.js App Router. One codebase → type-safe clients, OpenAPI, and AI tools.

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 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 22+, Next.js 15+ and TypeScript 5.3+.   Quick Start · Manual Install · Claude Plugin · 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']) { return prisma.user.findUnique({ where: { id } }); } }

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 generation (experimental)NPM Versiondev (optional)
vovk-rustRust client generation (experimental)NPM Versiondev (optional)

See Packages.


Claude Plugin

The official Claude Code plugin has 15 topic skills that teach the coding agent Vovk.ts as you describe what to build. A skill loads only when needed: “scaffold a tenant” loads the multitenant skill, “stream chat tokens” the JSON Lines one.

Install it inside Claude Code:

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

See Claude Plugin for the full skill list and how the framework’s layout helps the agent.


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