Secure, typesafe Hono APIs. Compiled.

Define your data model.
Get a multi-tenant, role-based, audit-logged API in seconds — and your week back.

PII masking Field validation State workflows Soft delete Batch operations Field-level views Filtering & search OpenAPI 3.1 Typed end-to-end MCP server Escape hatches

Now go build the features that actually matter.

Built for the 2026 stack

Hono
Hono
Drizzle
Drizzle
Better Auth
Better Auth
Cloudflare
Cloudflare
Neon
Neon
Why Hono

Not a platform. A build-step.

Quickback compiles your schema and security rules into real Hono code on your infrastructure. Nothing runs on our servers. If you uninstall us tomorrow, your app still ships.

Policy in. Execution out.

Declarative policy becomes executable code.

Every Quickback rule — security, access, masking, workflow — is a TypeScript declaration that the compiler turns into the runtime code that enforces it. Not a runtime engine reading a config file. The enforcement is the build output.

Step 1

Declarative Policy

Schema, firewall, access, guards, masking, actions — one TypeScript file per resource.

Step 2

Compiler

Validates rules, resolves precedence, refuses to emit code that can be called without its declared security.

Step 3

Enforced Execution

Hono routes, Drizzle constraints, Better Auth wiring, MCP tool guards — your policy, expressed as code.

No runtime policy engine to drift from your code. No separate service to keep in sync. The compiler is the policy enforcer — at build time, not request time.

Security, compiled in.

Four layers between your API and a breach. All declared. All compiled.

Firewall

Every table needs a scope column. Tenant isolation enforced at the database level.

Access

Role-based permissions. Deny by default. Compiled into middleware.

Guards

Field-level protection. Only permitted fields can be written.

Masking

PII redaction in responses. Sensitive data never leaks.

Plus a fifth pillar

Workflows

Actions + protected fields + record-level conditions, compiled into state transitions you can trust. Define approveInvoice or advance once — the compiler enforces who can transition, when, and which fields can change at each step.

See workflows

Schema in. Hono out.

We never connect to your database. We never see your data. We operate at the schema level.

From the same definitions, the compiler emits a typed Hono API, an MCP server for LLMs, Drizzle migrations, and Better Auth wiring — standard TypeScript you own.

Typed Hono API MCP server Drizzle migrations Better Auth wiring OpenAPI spec

SEE IT IN ACTION

Define once. Compile a secure Hono API — or RLS policies for existing Supabase.

quickback/features/candidates/candidates.ts
import { feature, q } from "@quickback/compiler";

export default feature("candidates", {
  columns: {
    id:             q.id(),
    name:           q.text().required().filterable("like").searchable(),
    email:          q.text().required().filterable("eq").searchable(),
    phone:          q.text().optional().filterable("eq"),
    resumeUrl:      q.text().optional(),
    source:         q.enum(["linkedin", "referral", "careers-page", "other"]).default("other").required().filterable("eq"),
    internalNotes:  q.text().optional().searchable(),
    organizationId: q.scope("organization"),
  },
  // firewall omitted — q.scope("organization") auto-derives
  // [{ field: "organizationId", equals: "ctx.activeOrgId" },
  //  { field: "deletedAt", isNull: true }]
  guards: {
    createable: ["name", "email", "phone", "resumeUrl", "source", "internalNotes"],
    updatable:  ["name", "email", "phone", "resumeUrl", "source", "internalNotes"],
  },
  masking: {
    email:         { type: "email",  show: { roles: ["admin+"] } },
    phone:         { type: "phone",  show: { roles: ["admin+"] } },
    internalNotes: { type: "redact", show: { roles: ["admin+"] } },
  },
  read: {
    access: { roles: ["member+"] },
    views: {
      pipeline: {
        fields: ["id", "name", "source"],
        access: { roles: ["member+"] },
        query: {
          filterable: ["source"],
          searchable: ["name"],
          sortable:   ["createdAt", "name"],
          defaultSort: "-createdAt",
        },
      },
      full: {
        fields: ["id", "name", "email", "phone", "resumeUrl", "source", "internalNotes"],
        access: { roles: ["admin+"] },
        query: {
          filterable: ["source", "email", "phone"],
          searchable: ["name", "email", "internalNotes"],
          sortable:   ["createdAt", "name", "email"],
        },
      },
    },
  },
  crud: {
    create: { access: { roles: ["admin+"] } },
    update: { access: { roles: ["admin+"] } },
    delete: { access: { roles: ["owner"] }, mode: "soft" },
  },
});
compiled output
// candidates.resource.ts — security helpers
import { eq, and, isNull, type SQL } from 'drizzle-orm';
import { candidates } from './schema';
import { masks } from '../../lib/masks';

// Auto-derived from q.scope("organization") + audit-injected deletedAt.
// 2 predicates: tenant scope AND soft-delete filter.
export function buildFirewallConditions(ctx: AppContext): SQL | undefined {
  return and(
    eq(candidates.organizationId, ctx.activeOrgId!),
    isNull(candidates.deletedAt),
  );
}

// q.scope() + audit fields → systemManaged. Always rejected from client input.
export const GUARDS_CONFIG = {
  createable: new Set(['name', 'email', 'phone', 'resumeUrl', 'source', 'internalNotes']),
  updatable:  new Set(['name', 'email', 'phone', 'resumeUrl', 'source', 'internalNotes']),
  systemManaged: new Set([
    'createdAt', 'createdBy', 'modifiedAt', 'modifiedBy',
    'deletedAt', 'deletedBy',
    'organizationId',                  // ← added by q.scope("organization")
  ]),
};

// Derived from masking[col].show.roles. Non-admins receive masked values.
export function maskCandidate<T extends Record<string, any>>(record: T, ctx: AppContext): T {
  const masked: any = { ...record };
  if (!ctx.roles?.some(r => ['admin', 'owner'].includes(r))) {
    if (masked.email != null)         masked.email = masks.email(masked.email);
    if (masked.phone != null)         masked.phone = masks.phone(masked.phone);
    if (masked.internalNotes != null) masked.internalNotes = masks.redact(masked.internalNotes);
  }
  return masked;
}

// candidates.routes.ts — unified read pipeline (excerpt)
const VIEWS = {
  pipeline: {
    fields: ['id', 'name', 'source'],
    access: { roles: ['member', 'admin', 'owner'] },
    query:  { filterable: ['source'], searchable: ['name'], sortable: ['createdAt', 'name'], defaultSort: '-createdAt' },
  },
  full: {
    fields: ['id', 'name', 'email', 'phone', 'resumeUrl', 'source', 'internalNotes'],
    access: { roles: ['admin', 'owner'] },
    // Masking query gate satisfied: full.access ∩ masking[*].show.roles = ['admin']
    query:  { filterable: ['source', 'email', 'phone'], searchable: ['name', 'email', 'internalNotes'], sortable: ['createdAt', 'name', 'email'] },
  },
} as const;

// GET /candidates/views/:viewName — named view projection
app.get('/views/:viewName', async (c) => {
  const ctx = c.get('ctx');
  if (!ctx.authenticated) return c.json(AuthErrors.required(), 401);

  const viewName = c.req.param('viewName');
  const params = Object.fromEntries(new URL(c.req.url).searchParams);
  const view = VIEWS[viewName as keyof typeof VIEWS];
  if (!view) return c.json({ error: 'Unknown view', code: 'VIEW_NOT_FOUND' }, 400);

  // Per-view access gate
  if (!await evaluateAccess(view.access, ctx)) {
    return c.json(AccessErrors.roleRequired(view.access.roles, ctx.roles), 403);
  }

  // Filter / sort / search are bounded by the view's query allowlist.
  // Masked columns (email, phone, internalNotes) are excluded unless the
  // view's query.{cap} explicitly lists them — validator already proved at
  // compile time that the view's access roles satisfy masking.show.roles.
  const filterCols = new Set(view.query.filterable ?? []);
  const sortCols   = new Set(view.query.sortable   ?? []);
  const searchCols = view.query.searchable         ?? [];

  const where: SQL[] = [];
  const fw = buildFirewallConditions(ctx);
  if (fw) where.push(fw);

  for (const [k, v] of Object.entries(params)) {
    const [field, op] = k.split('.');
    if (!filterCols.has(field) || !(field in candidates)) continue;
    const col = candidates[field as keyof typeof candidates];
    where.push(op === 'like'
      ? sql`${col} LIKE ${'%' + escapeLike(v) + '%'} ESCAPE '\\'`
      : eq(col, parseFilterValue(v)));
  }

  if (params.search && searchCols.length) {
    const term = '%' + escapeLike(params.search) + '%';
    where.push(or(...searchCols.map(c =>
      sql`${candidates[c as keyof typeof candidates]} LIKE ${term} ESCAPE '\\'`,
    ))!);
  }

  // …pagination, sort, project view.fields, run query…
  const rows = await db.select(/* projected fields */).from(candidates).where(and(...where));
  return jsonWithEtag(c, { data: maskCandidates(rows, ctx), view: viewName });
});

Try it now

npx @quickback-dev/cli create cloudflare my-app

BEYOND CRUD

Workflows the compiler enforces.

Define the workflow once. The compiler enforces who can transition, when, and which fields can change at each step — across REST, MCP, and the admin UI.

members/actions.ts
actions: {
  sendWelcomeEmail: {
    description: 'Send welcome email to new member',
    input: z.object({
      template: z.enum(['standard', 'vip']),
      personalNote: z.string().optional()
    }),
    access: {
      roles: ['admin', 'hr-manager'],
      record: { status: { equals: 'active' } }
    },
    execute: async ({ db, ctx, record, input }) => {
      await sendEmail({
        to: record.email,
        template: input.template,
        from: ctx.user.name
      });
      return { emailSent: true, sentBy: ctx.user.id };
    }
  }
}

Approvals & workflows

Multi-step operations like "approve invoice" or "close sprint" that enforce permissions and preconditions.

External integrations

Call any API — email providers, AI models, internal services — from typed Actions with full user context.

Webhooks & inbound events

Handle incoming webhooks as first-class operations, not one-off endpoints.

Queues & async work

Trigger background jobs, scheduled tasks, and fire-and-forget operations from any Action.

AI help is at the ready.

Install the CLI to access everything you need to define your backend.

Compiler CLI

Scaffold, compile, and deploy from the command line. Templates for every stack.

MCP Server

Connect any AI tool to the Quickback compiler. Schema-aware context for every prompt.

Claude Code Skills

AI-assisted backend development. Describe your feature, get a compiled API with security built in.

FAQ

Questions you're already asking

What happens when I need custom logic beyond CRUD?

That's what Actions are for. Define typed business operations - approve an invoice, stream an AI response, process a webhook - with the same security guarantees as your CRUD endpoints. Actions have full access to the database, user context, and any external service. There's no ceiling: if you can write it in TypeScript, you can ship it as an action.

Is there an escape hatch?

Several, by design. Actions cover anything beyond CRUD — your own typed handlers, wrapped in the same security layers. Every auto rule has an explicit exceptionfirewall.exception, access overrides, guard opt-outs — so a single resource can break a default without breaking the model. And the generated output is yours: patch it after compile, branch off entirely, or stop using Quickback and keep shipping with what you already have. The compiler is a build step, not a runtime — there's nothing to escape from.

What happens when I recompile? Do I lose my changes?

Recompiling overwrites the generated API code — and that's the point. Your definitions and actions are your source of truth. The generated src/ folder is output, like a build artifact. Change your definitions, recompile, and your entire API updates. Action handler files live in your project and are never overwritten — the compiler wraps them with authentication, access checks, and validation automatically.

How is this different from Supabase?

Quickback isn't competing with Supabase - the Compiler works with it, generating RLS from your definitions so you never write policies by hand. But if you want to own the whole stack, Quickback Stack gives you a complete Supabase-equivalent backend on your own Cloudflare account, and the Account UI gives you a ready-made auth frontend. Same definitions power all three.

Do I need Postgres? What databases are supported?

Quickback supports Cloudflare D1 (SQLite at the edge), Supabase Postgres (cloud or self-hosted), and Neon Postgres (serverless). The same definitions compile to different database targets - choose the one that fits your stack.

Am I locked into Quickback?

No. The output is standard TypeScript - Hono routes, Drizzle ORM, Better Auth. If you stop using Quickback tomorrow, you still have a working codebase built on well-known libraries. There's nothing proprietary in the generated code.

What if different roles need different fields?

Views are named field projections with role-based access control — column-level security. Define which fields each role can see, and the compiler generates a dedicated endpoint at /api/v1/{resource}/views/{viewName}. Views inherit all your security pillars — firewall, access, and masking — so a masked field stays masked even inside a view. Same pagination, filtering, and sorting as your list endpoint, just scoped to the fields you choose.

What is the Account UI?

A production-ready React SPA that plugs directly into your Quickback Stack or any Better Auth backend. Login, signup, passkeys, magic links, email OTP, organization management, role assignments, team creation, invitations, and an admin panel — out of the box. Deploy it as a Cloudflare Worker on your own subdomain. Fully customizable through environment variables.

How do Actions work across recompiles?

Actions are defined in your Quickback schema and the routes are regenerated on every compile — that's how they stay in sync with your security config. But your handler files are yours. They live in your project, the compiler never touches them. It just wraps them with authentication, access checks, input validation, and org scoping automatically. Write the business logic, Quickback handles the rest.

Private Beta Open

Join the Beta

We're onboarding early adopters now. Get hands-on access, direct support, and help shape the future of backend development.

Read the Docs