Skip to Content
vovk new

vovk new

Quick CLI Ref
npx vovk-cli new --help Usage: vovk new|n [options] [components...] Create new components. "vovk new [...components] [segmentName/]moduleName" to create a new module or "vovk new segment [segmentName]" to create a new segment Options: -o, --overwrite overwrite existing files --static (new segment only) if the segment is static --template, --templates <templates...> (new module only) override config template; accepts an array of strings that correspond to the order of the components --out, --out-dir <dirname> (new module only) override outDir in template file; relative to the root of the project --no-segment-update (new module only) do not update segment files when creating a new module --empty (new module only) create an empty module --dry-run do not write files to disk --log-level <level> set the log level -h, --help display help for command

The vovk new command creates segments and modules (such as controllers, services, or custom modules). It uses the moduleTemplates option from the config and can be extended with your own templates. It combines two workflows in one: vovk new segment [segment_name] and vovk new [module] [module_name_singular].

vovk new segment

vovk new segment

Root Segment

npx vovk new segment

If you run vovk new segment without an argument, it creates a root segment at /src/app/api/[[…vovk]]/route.ts (formatted with Prettier ). The segmentName option of initSegment is an empty string and can be omitted. The generated file contains:

import { initSegment } from 'vovk'; const controllers = {}; export type Controllers = typeof controllers; export const { GET, POST, PATCH, PUT, HEAD, OPTIONS, DELETE } = initSegment({ emitSchema: true, controllers, });

When you run vovk dev, the segment emits a schema at .vovk-schema/root.json.

The segment exposes an API at /api/….

Root segments can be used alongside nested segments of any depth.

Nested Segment

npx vovk new segment foo

Running vovk new segment foo creates /src/app/api/foo/[[…vovk]]/route.ts (formatted with Prettier). The generated file includes:

// ... export const { GET, POST, PATCH, PUT, HEAD, OPTIONS, DELETE } = initSegment({ segmentName: 'foo', emitSchema: true, controllers, });

When you run vovk dev, the segment emits a schema at .vovk-schema/foo.json.

The segment exposes an API at /api/foo/….


vovk new segment foo/bar/baz creates a nested segment at /src/app/api/foo/bar/baz/[[…vovk]]/route.ts, available at /api/foo/bar/baz/…. Here, segmentName is "foo/bar/baz".

vovk new [module] [name]

vovk new module
npx vovk new controller service foo/user

vovk new (with anything other than segment) creates new modules in /src/modules (formatted with Prettier).

Command structure:

  • npx vovk new — the command.
  • Components — the module types to create (controller, service, or a custom module).
  • Module name (singular) with optional segment prefix: foo/user creates a module in /src/modules/foo/user/ and updates the foo segment. Omit the segment to target the root:
npx vovk new controller service user

When you create a controller with vovk new, the script updates the controllers list in the segment file and modifies route.ts using AST .

Template paths are defined via moduleTemplates in the config:

vovk.config.mjs
/** @type {import('vovk-cli').VovkConfig} */ const config = { moduleTemplates: { controller: 'vovk-cli/module-templates/type/controller.ts.ejs', service: 'vovk-cli/module-templates/type/service.ts.ejs', state: './my-templates/state.ts.ejs', }, }; export default config;

npx vovk new controller state user creates UserController.ts and UserService.ts in /src/modules/user and updates the root segment with the new controller.

Built-in Module Templates

The built-in templates cover standard CRUD operations for controllers and services, including methods like get, list, create, update, and delete.

vovk init sets up a Vovk.ts project with the corresponding templates defined in the config. More specifically:

Shortcuts

Controllers and services can be created with shortcuts:

npx vovk n c s user

Which is equivalent to:

npx vovk new controller service user

Custom Module Templates

A module template is created with .ts.ejs extension. It uses EJS  syntax to generate code and gray-matter  frontmatter to define metadata in YAML format.

Module Template Metadata

The metadata supports the following fields:

  • outDir: string — output directory relative to the project root. A ejs variable t.defaultOutDir is available, which points to /src/modules/[segmentName/]moduleName/.
  • fileName: string — output file name.
  • sourceName: string — a controller name (applicable to controllers only); used to update the controllers list in the segment file
  • compiledName: string — an RPC module name (applicable to controllers only); used to define the name of the compiled module in the generated client.

Module Template Variables

Available variables are passed to the EJS template via the t object:

  • t.defaultOutDir: string — default output directory for the module.
  • t.config: VovkConfig — the Vovk.ts config.
  • t.segmentName: string — the segment name (empty string for the root segment).
  • t.withService: boolean — whether a service module is being created alongside the controller.
  • t.nodeNextResolutionExt: { ts: string; js: string; mjs: string; cjs: string } — file extension based on the moduleResolution in tsconfig.json. The object will contain extension values corresponding to the keys, such as .ts/.js/.mjs/.cjs for 'node16' and 'nodenext' and empty strings for other cases.
  • t.TheThing, t.TheThings — module name and pluralized module name in PascalCase (e.g., UserCart, UserCarts).
  • t.theThing, t.theThings — module name and pluralized module name in camelCase (e.g., userCart, userCarts).
  • t['the-thing'], t['the-things'] — module name and pluralized module name in kebab-case (e.g., user-cart, user-carts).
  • r.THE_THING, r.THE_THINGS — module name and pluralized module name in SCREAMING_SNAKE_CASE (e.g., USER_CART, USER_CARTS).
  • t._ - Lodash library instance for utility functions.
  • t.pluralize - Pluralize function from the pluralize package.

Controller & Service Template Example

Here is an example of a module template for an Arktype-based controller and service. For code clarity, internal variables are defined in vars object.

packages/vovk-cli/module-templates/arktype/controller.ts.ejs
<% const vars = { ModuleName: t.TheThing + 'Controller', ServiceName: t.TheThing + 'Service', }; %> --- outDir: <%= t.defaultOutDir %> fileName: <%= vars.ModuleName + '.ts' %> sourceName: <%= vars.ModuleName %> compiledName: <%= t.TheThing + 'RPC' %> --- import { procedure, prefix, get, put, post, del, operation } from 'vovk'; import { type } from 'arktype'; <% if(t.withService) { %> import <%= vars.ServiceName %> from './<%= vars.ServiceName %><%= t.nodeNextResolutionExt.ts %>'; <% } %> @prefix('<%= t['the-things'] %>') export default class <%= vars.ModuleName %> { @operation({ summary: 'Get <%= t.theThings %>', }) @get() static get<%= t.TheThings %> = procedure({ handle() { <% if(t.withService) { %> return <%= vars.ServiceName %>.get<%= t.TheThings %>(); <% } else { %> return { message: 'TODO: get <%= t.theThings %>' }; <% } %> } }); @operation({ summary: 'Get single <%= t.theThing %>', }) @get('{id}') static getSingle<%= t.TheThing %> = procedure({ params: type({ id: type('string') }), handle(_req, { id }) { <% if(t.withService) { %> return <%= vars.ServiceName %>.getSingle<%= t.TheThing %>(id); <% } else { %> return { message: 'TODO: get single <%= t.theThing %>', id }; <% } %> } }); @operation({ summary: 'Update <%= t.theThing %>', }) @put('{id}') static update<%= t.TheThing %> = procedure({ body: type({ todo: type('true') }), params: type({ id: type('string') }), async handle(req, { id }) { const body = await req.json(); <% if(t.withService) { %> return <%= vars.ServiceName %>.update<%= t.TheThing %>(id, body); <% } else { %> return { message: `TODO: update <%= t.theThing %>`, id, body }; <% } %> } }); @operation({ summary: 'Create <%= t.theThing %>', }) @post() static create<%= t.TheThing %> = procedure({ body: type({ todo: type('true') }), async handle(req) { const body = await req.json(); <% if(t.withService) { %> return <%= vars.ServiceName %>.create<%= t.TheThing %>(body); <% } else { %> return { message: `TODO: create <%= t.theThing %>`, body }; <% } %> } }); @operation({ summary: 'Delete <%= t.theThing %>', }) @del('{id}') static delete<%= t.TheThing %> = procedure({ params: type({ id: type('string') }), handle(_req, params) { const { id } = params; <% if(t.withService) { %> return <%= vars.ServiceName %>.delete<%= t.TheThing %>(id); <% } else { %> return { message: `TODO: delete <%= t.theThing %>`, id }; <% } %> } }); }

The code above is fetched from GitHub repository. 

When you run:

npx vovk new controller service userCart

It creates UserCartController.ts and UserCartService.ts in /src/modules/userCart/ and updates the root segment with the new controller.

src/modules/userCart/UserCartController.ts
import { procedure, prefix, get, put, post, del, operation } from 'vovk'; import { type } from 'arktype'; import UserCartService from './UserCartService.ts'; @prefix('user-carts') export default class UserCartController { @operation({ summary: 'Get userCarts', }) @get() static getUserCarts = procedure({ handle() { return UserCartService.getUserCarts(); }, }); @operation({ summary: 'Get single userCart', }) @get('{id}') static getSingleUserCart = procedure({ params: type({ id: type('string') }), handle(_req, { id }) { return UserCartService.getSingleUserCart(id); }, }); @operation({ summary: 'Update userCart', }) @put('{id}') static updateUserCart = procedure({ body: type({ todo: type('true') }), params: type({ id: type('string') }), async handle(req, { id }) { const body = await req.json(); return UserCartService.updateUserCart(id, body); }, }); @operation({ summary: 'Create userCart', }) @post() static createUserCart = procedure({ body: type({ todo: type('true') }), async handle(req) { const body = await req.json(); return UserCartService.createUserCart(body); }, }); @operation({ summary: 'Delete userCart', }) @del('{id}') static deleteUserCart = procedure({ params: type({ id: type('string') }), handle(_req, params) { const { id } = params; return UserCartService.deleteUserCart(id); }, }); }
Last updated on