Skip to Content
About Vovk.ts

Vovk.ts

Back-end Framework for Next.js App Router

GitHub Repo starsRuntime NPM VersionCLI NPM Version

Vovk.ts adds a structured API layer on top of Next.js App Router Route Handlers. Define endpoints once — as controllers with decorators — and the framework emits schema artifacts that generate type-safe clients, OpenAPI docs, and AI tool definitions. No separate contract layer to maintain.

npx vovk-cli@latest init

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


What it looks like

A controller is a class with HTTP-method decorators on static methods — a real Next.js Route Handler under the hood:

export default class UserController { @get('{id}') static async getUser(req: NextRequest, { id }: { id: string }) { // ... } }

Wrap it with procedure to validate and type params, query, and body in-place:

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

Services hold business logic separately. No decorators, just plain classes:

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

Codegen reads the emitted schema and produces a fetch-powered client with matching types:

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

Controllers and generated modules can be exposed as AI tools:

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

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

Endpoints can yield JSON Lines for real-time responses:

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

See JSON Lines.

Local procedure calls

Procedures can run directly on the server for SSR/PPR, skipping the HTTP round-trip:

const user = await UserController.getUser.fn({ params: { id: '123' } });

See Calling Procedures Locally.

Docs and publishing

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

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


Packages

PackageRoleInstall
vovkRuntime — decorators, procedure, routing, deriveToolsproduction
vovk-cliToolchain — codegen, mixins, docs, bundlingdev
vovk-clientRe-exported composed clientoptional

See Packages.


Example app

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.


Vocabulary

TermMeaning
ControllerClass with endpoint definitions via decorators on static methods
ProcedureA handler optionally wrapped by procedure for validation
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