Vovk.ts
Back-end Framework for Next.js App Router
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 initRequires 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.
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
| Package | Role | Install |
|---|---|---|
vovk | Runtime — decorators, procedure, routing, deriveTools | production |
vovk-cli | Toolchain — codegen, mixins, docs, bundling | dev |
vovk-client | Re-exported composed client | optional |
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
| Term | Meaning |
|---|---|
| Controller | Class with endpoint definitions via decorators on static methods |
| Procedure | A handler optionally wrapped by procedure for validation |
| Segment | A routed back-end slice, compiled independently into its own function |
| RPC module | Generated client module mirroring a controller |
| API module | Generated module from a controller or an OpenAPI schema |