Vovk.ts
Back-end Framework for Next.js App Router
Back-end Framework for Next.js App Router. One codebase → type-safe clients, OpenAPI, and AI tools.
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 initRequires 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,inputSchemaandexecute, fromderiveTools - a generated
README.mdthat 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.
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
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-pluginsSee 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
| Term | Meaning |
|---|---|
| Controller | A class that groups procedures as static members; HTTP decorators serve them as endpoints |
| Procedure | A 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 |
| Segment | A part of the back end with its own route and function |
| RPC module | Generated client module that mirrors a controller |
| API module | Generated module from a controller or an OpenAPI schema |