Getting started

Quickstart

Scaffold a project, start the worker runtime, and serve your first route. Two minutes, no configuration files.

Scaffold a project

The CLI writes a complete project — src/main.ts, the yatta/func/ directory, and every subsystem already wired to its own worker pool. It then runs bun install for you.

terminalts
bunx yatta new my-appcd my-appbun run dev

You should see the runtime mount all eight subsystems and bind a port:

tsts
✓ Mounted 8 subsystems (auth, jobs, db, cache, mail, storage, events, cron)Yatta server running at http://localhost:4000 (PID: 4711)
Note
The port is 4000 by default. Set PORT to change it — see Configuration.

Write your first route

Routes are files. Drop one in yatta/backend/ and it is served on the next request — there is no router to register it with.

yatta/backend/hello.tsts
import { createAPI } from "yatta/api"; const hello = createAPI("/hello"); hello.get((ctx) => {  return Response.json({ message: "Hello from Yatta" });}); export default hello;
terminalts
$ curl http://localhost:4000/hello{"message":"Hello from Yatta"}

The path passed to createAPI is typed, so ctx.params is inferred from the literal rather than declared by hand.

Add data

Declare a schema and the ORM builds the tables on boot. There is no migration file to write for a new schema.

yatta/func/db.tsts
import { col, createDatabase } from "yatta/db"; export const schema = {  notes: {    id: col.uuid(),    title: col.text(),    body: col.text().nullable(),    createdAt: col.createdAt(),  },}; export const db = createDatabase({  url: process.env.DATABASE_URL,  schema,});
yatta/backend/notes.tsts
import { createAPI } from "yatta/api";import { db } from "../func/db"; const notes = createAPI("/notes"); // Tables are properties on the handle: db.notes, not a select().from().notes.get(async () => {  const rows = await db.notes    .where((f) => f.archived.isEqualTo(false))    .orderBy({ createdAt: "desc" })    .limit(50)    .all();   return Response.json(rows);}); notes.post(async (ctx) => {  const body = await ctx.json();  const note = await db.notes.insert(body);  return Response.json(note, { status: 201 });}); export default notes;

Move slow work off the request

Anything slow — sending mail, calling a third-party API, generating a report — belongs on a job. The handler runs on a worker, so the request returns as soon as the job is queued.

yatta/func/jobs.tsts
import { createJobs } from "yatta/jobs"; export const jobs = createJobs(); export interface JobHandlers {  "send-welcome": { to: string };} jobs.handle("send-welcome", async ({ to }) => {  await mail.send({ to, subject: "Welcome", template: "welcome" });});
tsts
await jobs.enqueue("send-welcome", { to: user.email });

Check the build

yatta check runs the same validation in CI — TypeScript across the project, plus a boot of the runtime to prove the schema applies cleanly.

terminalts
bun run yatta check
Tip
Run yatta check before every deploy. It catches the two failures that are otherwise only visible in production: a schema that will not apply, and a worker that cannot mount.

Every command

  • yatta new <name> — scaffold a project. yatta new . scaffolds into the current directory.
  • yatta dev — start with hot reload.
  • yatta start — start in production mode.
  • yatta build — compile for deployment.
  • yatta cluster — spawn one worker process per core, bound to the same port with SO_REUSEPORT.
  • yatta check — typecheck and boot-verify.
  • yatta info — print the resolved environment and topology.
  • yatta link / yatta unlink — manage the global link to the framework itself.