Vercube 1.5 is out. The framework gets a typed client, typed errors, health probes, a project generator with modules and a security pass. The site was redesigned for the release too: homepage, docs, blog and changelog. Drag the divider to compare the old homepage with the new one.

1.41.5Search is in the header, the homepage opens on create-vercube running instead of a code sample, and the diagrams in the docs were redrawn to match. The content itself is unchanged.
A client typed by your controllers
@vercube/client is a fetch wrapper that takes its types from your controllers, so there is no separate API description to keep in sync. The response is what the handler returns, the body is what @Body() accepts, and the path parameters are the :segments of the route.
const api = createClient<VercubeRoutes>({ baseURL: 'http://localhost:3000' });
const user = await api.get('/users/:id', { params: { id: '42' } });
// ^? { id: string; name: string; createdAt: string }
Rename a field in a handler and every call that no longer fits stops compiling. The route map it relies on is generated from the running app by vercube dev, vercube build and the Vite plugin, and holds no types of its own, so it is small enough to commit and review. About 1.5 kB gzipped, and it also powers app.client in @vercube/testing.
Read more: Client.
Typed errors
Declare what a route can answer with, and the caller gets it as a union instead of catch (e: unknown):
@Put('/:id/email')
@Throws(NotFoundError, ConflictError)
public changeEmail(@Param('id') id: string, @Body() body: { email: string }) {}
const res = await api.safe.put('/users/:id/email', { params: { id }, body });
if (res.error?.status === 409) {
pickAnother(res.error.body.existingId); // the ConflictError's own field
}
The same declarations land in the OpenAPI document that @vercube/schema generates.
Read more: Typed errors.
Health checks and rolling deploys
export default defineConfig({ health: true });
That gives you /_health/live, /ready and /startup, readiness checks registered by the queue, the scheduler and S3 storage, and a shutdown that answers 503 first and keeps serving for a few seconds, so a Kubernetes rolling update drops no requests.
Read more: Health checks.
A project generator with modules
pnpm create vercube my-app --template fullstack-vue --modules schema,auth,queue,testing
pnpm vercube add cache,ws
create-vercube now assembles a project from modules, each one a recipe that edits your config and container, adds a sample route and its test, and brings the Redis or RabbitMQ it needs. vercube add applies the same recipes to a project that already exists, and running one twice changes nothing. The new fullstack-vue template is an API and a Vue app in one workspace, wired together through the typed client.
Read more: Project generator · Writing a recipe.
Security pass
After 1.4 I audited the parts of the framework that take input from the network: error responses, the schema endpoint, WebSocket upgrades, static files, telemetry and auth. The changes you are most likely to notice:
- Unexpected errors no longer echo their message or
causeoutside development. /_schemais not served in production by default. Turn it on withenabled: true, behind atoken.- WebSocket upgrades check
Origin, andallowedOriginslets a frontend on another origin in. - Static files ignore dotfiles and refuse symlinks that leave the directory.
- Telemetry redacts credential-named headers and body keys.
- Auth fails closed on an invalid
validate()result, andvalidate()can return{ user }so@User()does not load it a second time.
The rest, around thirty fixes in all, is in the changelog. Devtools also got the new look, and its state-changing endpoints now refuse cross-site requests.
Upgrade
Every @vercube/* package is released together and has to be on the same version:
pnpm up "@vercube/*" --latest
pnpm add @vercube/[email protected]
Most applications upgrade without touching anything. A few behaviours changed on purpose:
/_schemais not served whenproduction: true. Setenabled: trueand atokento keep it.- A handler that returns
undefinedanswers204, not200. - Unexpected errors no longer echo their message to the caller outside development. Look in your logs or in devtools.
- WebSocket upgrades from another origin get
403. Requests without anOriginheader are not affected. List a frontend on another origin inallowedOrigins. healthchanges two defaults when on:shutdown.drainDelaybecomes 5000 andshutdown.handleSignalsbecomestrue. Setting either explicitly wins.
I would like to hear how the typed client works in a real monorepo, and which modules the generator should support next. Issues and discussions are on GitHub, and I am on X.
