Skip to content

Functions

Functions are the server-side logic of your Zeroback application. They are defined in .ts files inside the zeroback/ directory and are automatically discovered and bundled by the CLI.

TypeCan read DBCan write DBCan call APIsCallable from clientReal-time
queryYesNoNoYesYes (subscriptions)
mutationYesYesNoYesNo
actionNo (via runQuery)No (via runMutation)YesYesNo
internalQueryYesNoNoNo (server only)No
internalMutationYesYesNoNo (server only)No
internalActionNo (via runQuery)No (via runMutation)YesNo (server only)No

Import the typed function factories from zeroback/_generated/server (generated by codegen):

import { query, mutation, action } from "./_generated/server";
import { internalQuery, internalMutation, internalAction } from "./_generated/server";
import { v } from "@zeroback/server";

Defines a read-only query. Queries are reactive — clients subscribing to a query receive real-time updates when the underlying data changes.

query({
args: { /* validators */ },
returns?: Validator,
handler: async (ctx, args) => { /* ... */ },
})
FieldTypeRequiredDescription
argsRecord<string, Validator>YesArgument validators. Validated at runtime.
returnsValidatorNoReturn type validator. If set, the return value is validated at runtime.
handler(ctx: QueryCtx, args) => Promise<T>YesThe query function.

QueryCtx provides:

Example:

export const listByProject = query({
args: { projectId: v.string() },
handler: async (ctx, args) => {
return await ctx.db
.query("tasks")
.withIndex("by_project", (q) => q.eq("projectId", args.projectId))
.order("desc")
.take(100);
},
});

Defines a function that can read and write to the database. Mutations are transactional — they run inside an OCC transaction that automatically retries on conflict.

mutation({
args: { /* validators */ },
returns?: Validator,
handler: async (ctx, args) => { /* ... */ },
})
FieldTypeRequiredDescription
argsRecord<string, Validator>YesArgument validators
returnsValidatorNoReturn type validator
handler(ctx: MutationCtx, args) => Promise<T>YesThe mutation function

MutationCtx provides:

  • ctx.dbDatabaseWriter (read + write database access)
  • ctx.schedulerScheduler (schedule future function calls)
  • ctx.storageStorageWriter (read + write file storage access)

Example:

export const create = mutation({
args: {
title: v.string(),
status: v.string(),
projectId: v.string(),
},
handler: async (ctx, args) => {
return await ctx.db.insert("tasks", args);
},
});

Defines a function that can call external APIs and invoke other functions. Actions do not have direct database access — they call queries and mutations via ctx.runQuery() and ctx.runMutation().

action({
args: { /* validators */ },
returns?: Validator,
handler: async (ctx, args) => { /* ... */ },
})
FieldTypeRequiredDescription
argsRecord<string, Validator>YesArgument validators
returnsValidatorNoReturn type validator
handler(ctx: ActionCtx, args) => Promise<T>YesThe action function

ActionCtx provides:

  • ctx.runQuery(fnName, args?) — Call a query function
  • ctx.runMutation(fnName, args?) — Call a mutation function
  • ctx.runAction(fnName, args?) — Call another action function
  • ctx.schedulerScheduler
  • ctx.storageStorageActions (full storage access including store())

The fnName format is "module:functionName" — e.g., "tasks:create", "utils/stats:taskStats".

Example:

export const createViaAction = action({
args: {
title: v.string(),
status: v.string(),
projectId: v.string(),
},
handler: async (ctx, args) => {
await ctx.runMutation("tasks:create", args);
const tasks = await ctx.runQuery("tasks:listByProject", {
projectId: args.projectId,
});
return { created: true, count: tasks.length };
},
});

internalQuery(config) / internalMutation(config) / internalAction(config)

Section titled “internalQuery(config) / internalMutation(config) / internalAction(config)”

Same signature as their public counterparts, but cannot be called from the client. They can only be invoked server-side via ctx.runQuery(), ctx.runMutation(), ctx.runAction(), or via the scheduler.

export const countInternal = internalQuery({
args: { projectId: v.string() },
handler: async (ctx, args) => {
const tasks = await ctx.db
.query("tasks")
.withIndex("by_project", (q) => q.eq("projectId", args.projectId))
.collect();
return tasks.length;
},
});

Functions are named based on their file path and export name:

FileExportFunction Name
zeroback/tasks.tscreate"tasks:create"
zeroback/tasks.tslistByProject"tasks:listByProject"
zeroback/utils/stats.tstaskStats"utils/stats:taskStats"

Files that are excluded from function discovery:

  • zeroback/schema.ts
  • zeroback/_generated/*
  • Files starting with _

Use the optional returns field to validate the return value at runtime:

export const countByProject = query({
args: { projectId: v.string() },
returns: v.number(),
handler: async (ctx, args) => {
const tasks = await ctx.db
.query("tasks")
.withIndex("by_project", (q) => q.eq("projectId", args.projectId))
.collect();
return tasks.length;
},
});

HTTP actions expose your functions as REST endpoints. Define them in a file that exports a default HttpRouter.

Creates a new HTTP router instance.

import { httpRouter, httpAction } from "@zeroback/server";
const http = httpRouter();
// ... register routes ...
export default http;

Wraps a handler function for use with the router.

function httpAction(
handler: (ctx: ActionCtx, request: Request) => Promise<Response>
): HttpAction
ParameterTypeDescription
ctxActionCtxAction context with runQuery, runMutation, runAction, scheduler, storage
requestRequestStandard Web API Request object

Must return a standard Web API Response object.

Register an exact-path route.

http.route({
path: string,
method: string,
handler: HttpAction,
})
FieldTypeDescription
pathstringExact path to match (e.g., "/api/tasks")
methodstringHTTP method ("GET", "POST", "PUT", "DELETE", etc.)
handlerHttpActionAn httpAction() wrapper

Register a prefix-based route. Matches any path that starts with the given prefix.

http.routeWithPrefix({
pathPrefix: string,
method: string,
handler: HttpAction,
})

Exact routes take priority over prefix routes when both match.

import { httpRouter, httpAction } from "@zeroback/server";
const http = httpRouter();
http.route({
path: "/api/tasks",
method: "GET",
handler: httpAction(async (ctx, request) => {
const url = new URL(request.url);
const projectId = url.searchParams.get("projectId") ?? "";
const tasks = await ctx.runQuery("tasks:listByProject", { projectId });
return new Response(JSON.stringify(tasks), {
headers: { "Content-Type": "application/json" },
});
}),
});
http.route({
path: "/api/tasks",
method: "POST",
handler: httpAction(async (ctx, request) => {
const body = await request.json();
await ctx.runMutation("tasks:create", body);
return new Response(JSON.stringify({ ok: true }), {
headers: { "Content-Type": "application/json" },
});
}),
});
export default http;

Cron jobs run functions on a recurring schedule. Define them in a file that exports a default CronJobs instance.

Creates a new cron jobs instance.

import { cronJobs } from "@zeroback/server";
const crons = cronJobs();
// ... register jobs ...
export default crons;

crons.interval(name, schedule, fnName, args?)

Section titled “crons.interval(name, schedule, fnName, args?)”

Run a function at a fixed interval.

crons.interval(
name: string,
schedule: { hours?: number; minutes?: number; seconds?: number },
fnName: string,
args?: unknown
)
ParameterTypeDefaultDescription
namestringUnique job name
scheduleobjectInterval duration. At least one field must be positive.
schedule.hoursnumber0Hours component
schedule.minutesnumber0Minutes component
schedule.secondsnumber0Seconds component
fnNamestringFunction to call (e.g., "tasks:cleanup")
argsunknown{}Arguments to pass to the function

crons.cron(name, expression, fnName, args?)

Section titled “crons.cron(name, expression, fnName, args?)”

Run a function on a standard 5-field cron schedule (UTC).

crons.cron(name: string, expression: string, fnName: string, args?: unknown)
ParameterTypeDefaultDescription
namestringUnique job name
expressionstring5-field cron expression: "minute hour dayOfMonth month dayOfWeek"
fnNamestringFunction to call
argsunknown{}Arguments to pass

Run a function once per hour.

ParameterTypeDefaultDescription
opts.minuteUTCnumber0Minute of the hour (0-59)

Run a function once per day.

ParameterTypeDefaultDescription
opts.hourUTCnumber0Hour of the day (0-23)
opts.minuteUTCnumber0Minute of the hour (0-59)

Run a function once per week.

ParameterTypeDefaultDescription
opts.dayOfWeeknumber1 (Monday)Day of week (0=Sun, 1=Mon, …, 6=Sat)
opts.hourUTCnumber0Hour of the day
opts.minuteUTCnumber0Minute of the hour

Run a function once per month.

ParameterTypeDefaultDescription
opts.dayOfMonthnumber1Day of the month (1-31)
opts.hourUTCnumber0Hour of the day
opts.minuteUTCnumber0Minute of the hour
import { cronJobs } from "@zeroback/server";
const crons = cronJobs();
// Every 5 seconds
crons.interval("cleanup", { seconds: 5 }, "tasks:cleanupDone", {});
// Every day at 9:00 UTC
crons.daily("digest", { hourUTC: 9 }, "email:sendDigest");
// Every Monday at 8:00 UTC
crons.weekly("report", { dayOfWeek: 1, hourUTC: 8 }, "reports:weekly");
// 1st of every month at midnight
crons.monthly("billing", { dayOfMonth: 1 }, "billing:charge");
// Custom cron expression (weekdays at 3:00 AM UTC)
crons.cron("nightly", "0 3 * * 1-5", "jobs:nightly");
export default crons;

Running zeroback dev, zeroback deploy, or zeroback codegen generates three files in zeroback/_generated/:

Typed function references for the client. Each function becomes a property on the api object:

import { api } from "../zeroback/_generated/api";
// api.tasks.create — FunctionReference<"mutation", { title: string, ... }>
// api.tasks.listByProject — FunctionReference<"query", { projectId: string }>

Internal functions are available on the internal object:

import { internal } from "../zeroback/_generated/api";
// internal.tasks.countInternal — FunctionReference<"query", { projectId: string }>

Typed function factories bound to your DataModel:

import { query, mutation, action } from "./_generated/server";
import { internalQuery, internalMutation, internalAction } from "./_generated/server";

These provide full type safety — ctx.db.query("tasks") knows the shape of your tasks table.

The DataModel type mapping table names to their document types:

type DataModel = {
tasks: {
_id: string;
_creationTime: number;
title: string;
status: string;
// ...
};
// ...
};