Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.vertz.dev/llms.txt

Use this file to discover all available pages before exploring further.

@vertz/server is the backend layer of the Vertz stack. You define entities with schemas, access rules, and hooks — the framework generates typed CRUD routes, validates inputs, enforces access control, and produces a typed client SDK. No boilerplate controllers, no manual route wiring.

How it works

1

Define your entity

Declare the data model, who can do what, and any side effects (hooks). One object, one place.
2

Framework generates routes

CRUD endpoints are auto-generated with validation, access enforcement, and error handling built in.
3

SDK is generated

A fully typed client SDK is produced from your entity definitions — used by the UI layer with query() and form().

What’s included

FeatureDescription
EntitiesDeclarative data models with CRUD, access rules, hooks, and custom actions
DomainsGroup entities and services into bounded contexts with automatic route prefixing
Field & relation exposureControl which fields, filters, and relations are exposed in query responses
ServicesStandalone operations not tied to a single entity
Access controlDeny-by-default, per-operation access rules with row-level checks
EnvironmentValidated, typed, frozen config via createEnv()
AuthenticationEmail/password, JWT sessions, refresh rotation, OAuth providers
MiddlewareRequest pipeline with typed context propagation
SDK generationAuto-generated typed client from entity definitions

Quick example

import { createServer, entity, rules } from '@vertz/server';
import { createDb, d } from '@vertz/db';

const tasksModel = d.model(
  d.table('tasks', {
    id: d.uuid().primary({ generate: 'cuid' }),
    title: d.text(),
    status: d.enum('task_status', ['todo', 'in_progress', 'done']).default('todo'),
    createdAt: d.timestamp().default('now').readOnly(),
  }),
);

const tasks = entity('tasks', {
  model: tasksModel,
  access: {
    list: rules.public,
    get: rules.public,
    create: rules.public,
    update: rules.public,
    delete: rules.public,
  },
});

const db = createDb({
  url: process.env.DATABASE_URL!,
  models: { tasks: tasksModel },
});

createServer({ entities: [tasks], db }).listen({ port: 3000 });
This generates:
GET    /api/tasks          → list (with filtering, pagination)
GET    /api/tasks/:id      → get
POST   /api/tasks          → create
PATCH  /api/tasks/:id      → update
DELETE /api/tasks/:id      → delete
No route handlers to write. Validation, access checks, and error responses are handled automatically.

API prefix

By default, all routes are mounted under /api/. You can change this with the apiPrefix option:
createServer({
  entities: [tasks],
  db,
  apiPrefix: '/v1',
});
This generates routes under /v1/ instead:
GET    /v1/tasks          → list
POST   /v1/tasks          → create
GET    /v1/auth/signin    → sign-in page
...

Defaults and normalization

InputResolved prefix
undefined/api
'/v1'/v1
'/api/v2/'/api/v2
'/''' (root)
Trailing slashes are stripped automatically. A bare '/' is treated as an empty prefix (root mount).

API-only apps

If your server has no UI layer, you can remove the prefix entirely:
createServer({
  entities: [tasks],
  db,
  apiPrefix: '',
});
Routes mount at the root: GET /tasks, POST /tasks, etc.
Empty apiPrefix is only allowed in API-only mode. Full-stack apps (with SSR) require a non-empty prefix so the server can distinguish API requests from page navigations.

Auth routes

Auth routes automatically follow the prefix. If apiPrefix is '/v1', auth routes are served at /v1/auth/* and session cookies are scoped to Path=/v1/auth/....

Cloudflare Workers

When deploying to Cloudflare, createHandler reads apiPrefix from your server automatically. You only need to set it explicitly if you want to override:
createHandler({
  app: (env) => createServer({ apiPrefix: '/v1' }),
  ssr: { module: app },
  // apiPrefix is auto-read from app — no need to repeat it
});
See the Cloudflare Workers guide for details.

Core principles

Entity-driven

Entities are the central abstraction. A single entity definition covers the data model, validation, access control, lifecycle hooks, and custom actions. Everything flows from the entity.

Deny-by-default

No access rule = no route generated. You explicitly declare who can perform each operation. There’s no “open by default” — you opt in to access, not out.

Type-safe end-to-end

Types flow from your schema definition through the server, into the generated SDK, and down to the UI layer. If it compiles, the types are correct across the entire stack.

Guides

Entities

Models, access rules, hooks, and custom actions.

Domains

Group entities and services into bounded contexts with route prefixing.

Fields, Relations & Filters

Control which fields, filters, and relations clients can query.

Authentication

Email/password, JWT sessions, refresh tokens, and session management.

OAuth Providers

Add Google, GitHub, or Discord sign-in.

Services

Standalone operations and cross-entity workflows.

Environment

Validate and type-check env variables with createEnv().