CLI Guide
CLI Guide
Section titled “CLI Guide”Veloce-TS ships with a built-in CLI for project creation and code generation. Every generated file passes npm run typecheck out of the box, and since v3.1.0 everything the CLI produces runs on Node as well as Bun.
Installation
Section titled “Installation”The veloce and veloce-ts binaries are available after installing the package:
npm install veloce-ts# or globallynpm install -g veloce-ts
# Bun works the same waybun add veloce-tsveloce new <name>
Section titled “veloce new <name>”Scaffolds a complete project.
veloce new my-apiGenerated structure (the rest template; each one writes only the directories it fills):
my-api/├── src/│ ├── index.ts # app entry with usePlugin calls│ ├── controllers/│ │ └── user.controller.ts # injects UserService with @Inject│ └── services/│ └── user.service.ts # resolved through the DI container├── public/│ └── docs.html # standalone Swagger UI├── package.json # scripts route through the veloce binary├── tsconfig.json # Stage-2 decorators + reflect-metadata├── .env.example # every variable the generated code reads├── .gitignore└── README.mdThe graphql and fullstack templates add src/resolvers/, websocket and fullstack add src/websockets/, and all of them except websocket add src/services/.
Options:
| Option | Default | What it does |
|---|---|---|
-t, --template <template> | rest | rest, graphql, websocket or fullstack |
--install | off | Install dependencies (Bun when available, npm otherwise) |
--git | off | git init plus an initial commit — skipped when the target is already inside a repository |
The examples use the DI container. The generated controller and resolver ask for their UserService with @Inject(UserService) rather than constructing it, so replacing the in-memory array with a database leaves every caller untouched. Scaffolding that news up its dependencies teaches the opposite of what the framework provides.
Templates:
veloce new my-api # REST (default)veloce new my-api --template restveloce new my-api --template graphql # GraphQL resolver + playgroundveloce new my-api --template websocket # WebSocket gatewayveloce new my-api --template fullstack # REST + GraphQL + WebSocket:::note All four templates run on Node
Including websocket and fullstack, which used to throw at startup there. They
declare @hono/node-ws (unused under Bun and Deno, which upgrade natively). CI
scaffolds every template and installs, type-checks, builds and boots it under Node
on each run.
:::
The generated package.json routes its scripts through the veloce binary rather
than hardcoding a runtime, so the same project works either way:
{ "scripts": { "dev": "veloce dev", "build": "veloce build", "start": "node dist/index.js", "typecheck": "tsc --noEmit" }}veloce dev
Section titled “veloce dev”Starts the app with hot reload.
veloce devveloce dev --port 8080veloce dev --runtime node # force the Node pathUnder Bun it uses bun --watch. Under Node it runs tsx, which transpiles the
Stage-2 decorators Node’s own type stripping cannot handle — tsx is included as
a devDependency in generated projects.
| Option | Default | What it does |
|---|---|---|
-p, --port <port> | 3000 | Port passed to the app as PORT |
-w, --watch <path> | src | Extra paths to watch |
-r, --runtime <runtime> | auto | auto, bun or node |
veloce build
Section titled “veloce build”Compiles the project for production.
veloce buildveloce build --runtime nodeveloce build --outdir buildUnder Bun it bundles with Bun.build and emits ESM and CJS. Under Node it compiles
with the project’s own tsc, honouring your tsconfig.json — which is what emits
the decorator metadata the framework reads.
:::note The Bun path needs the CLI itself to be running under Bun
Bun.build is an in-process API, so having the bun binary on PATH is not
enough: npm run build on a machine with Bun installed still takes the Node path,
because npm starts the CLI with Node. Use bun run build for the Bun path, or pass
--runtime bun (which errors if the process is not Bun).
:::
| Option | Default | What it does |
|---|---|---|
-o, --outdir <dir> | dist | Output directory |
-m, --minify | off | Minify (Bun path) |
-s, --sourcemap | off | Emit sourcemaps. Off by default: shipping them is a deployment-size decision, and the framework’s own package was 73% sourcemaps before that lesson landed in 3.1.2 |
-f, --format <format> | both | esm, cjs or both — Bun path only |
-r, --runtime <runtime> | auto | auto, bun or node |
veloce generate
Section titled “veloce generate”Generate individual pieces of your application. All commands accept a <name> argument.
generate module <name>
Section titled “generate module <name>”Generates a full module folder: controller + service + DTO + barrel.
veloce generate module usersOutput in src/modules/users/:
users.controller.ts # @Controller('/users') with CRUD stubsusers.service.ts # plain service classusers.dto.ts # CreateUsersDto, UpdateUsersDto (Zod)users.module.ts # barrel: re-exports controller + servicegenerate controller <name>
Section titled “generate controller <name>”veloce generate controller productsimport { Controller, Get, Post, Put, Delete, Param, Body } from 'veloce-ts';import { z } from 'zod';
const CreateProductsSchema = z.object({ name: z.string().min(1),});
@Controller('/products')export class ProductsController { @Get('/') async findAll() { return []; }
@Get('/:id') async findOne(@Param('id') id: string) { return { id }; }
@Post('/') async create(@Body(CreateProductsSchema) body: z.infer<typeof CreateProductsSchema>) { return body; }
@Put('/:id') async update(@Param('id') id: string, @Body() body: unknown) { return { id, ...body as object }; }
@Delete('/:id') async remove(@Param('id') id: string) { return { id, deleted: true }; }}generate service <name>
Section titled “generate service <name>”veloce generate service productsexport class ProductsService { async findAll() { return []; }
async findOne(id: string) { return { id }; }
async create(data: unknown) { return data; }
async update(id: string, data: unknown) { return { id, ...data as object }; }
async remove(id: string) { return { id, deleted: true }; }}generate dto <name>
Section titled “generate dto <name>”veloce generate dto create-productimport { z } from 'zod';
export const CreateProductSchema = z.object({ name: z.string().min(1),});
export type CreateProductDto = z.infer<typeof CreateProductSchema>;generate resolver <name>
Section titled “generate resolver <name>”veloce generate resolver userimport { Resolver, GQLQuery, GQLMutation, Arg } from 'veloce-ts/graphql';import { z } from 'zod';
@Resolver('user')export class UserResolver { @GQLQuery('getUsers') async getAll() { return []; }
@GQLQuery('getUser') async getOne(@Arg('id', z.string()) id: string) { return { id }; }
@GQLMutation('createUser') async create(@Arg('name', z.string()) name: string) { return { id: Date.now().toString(), name }; }}generate middleware <name>
Section titled “generate middleware <name>”veloce generate middleware authimport type { Context, Next } from 'hono';
export async function authMiddleware(c: Context, next: Next) { const token = c.req.header('authorization'); if (!token) { return c.json({ error: 'Unauthorized' }, 401); } await next();}generate gateway <name>
Section titled “generate gateway <name>”A @WebSocket gateway with the three lifecycle hooks and a Zod message schema.
veloce generate gateway presenceCreates src/websockets/presence.websocket.ts with @OnConnect, @OnMessage and @OnDisconnect, joining a room named after the gateway. Register it like a controller:
app.include(PresenceGateway);app.usePlugin(new WebSocketPlugin());Only one @OnMessage per gateway — the metadata stores a single message handler — so model different client payloads as a discriminated union on type.
generate listener <name>
Section titled “generate listener <name>”An @On event-listener class.
veloce generate listener auditCreates src/listeners/audit.listener.ts. Listeners are registered with app.include() and resolved through the DI container, so they inject services exactly like a controller. Subscriptions attach at compile() and are removed at shutdown().
generate plugin <name>
Section titled “generate plugin <name>”veloce generate plugin analyticsimport type { Plugin, VeloceTS } from 'veloce-ts';
export class AnalyticsPlugin implements Plugin { name = 'analytics';
async register(app: VeloceTS): Promise<void> { // register routes or middleware here }}Placement rules
Section titled “Placement rules”| Command | Output path |
|---|---|
generate module <name> | src/modules/<name>/ |
generate controller <name> | src/controllers/<name>.controller.ts |
generate service <name> | src/services/<name>.service.ts |
generate dto <name> | src/dto/<name>.dto.ts |
generate resolver <name> | src/resolvers/<name>.resolver.ts |
generate middleware <name> | src/middleware/<name>.middleware.ts |
generate plugin <name> | src/plugins/<name>.plugin.ts |
generate gateway <name> | src/websockets/<name>.websocket.ts |
generate listener <name> | src/listeners/<name>.listener.ts |
The CLI detects an existing src/ directory and places files there. If src/ is not found, files are placed in the current directory.
Name normalization
Section titled “Name normalization”The <name> argument is normalized automatically:
| Input | PascalCase | kebab-case |
|---|---|---|
user | User | user |
UserProfile | UserProfile | user-profile |
create-product | CreateProduct | create-product |