Quickstart
From an empty folder to a live environment, then change an action and watch it update.
Create an app
npx @domainruntime/cli create-app my-app --next
cd my-app
npx druntime # or: npm run localdruntime opens your browser to sign in — or create an account, which gives you a personal organization — and creates a project and a personal development environment the first time. Then it:
- publishes
src/domain.tsto your environment, - writes the environment's URL into
.env.localasDOMAIN_RUNTIME_URL, - starts your Next.js dev server,
- watches your domain and republishes on every save.
my-app · dev-my-app-dev-1a2b3c4d-7f3k9x2m
backend https://dev-my-app-dev-1a2b3c4d-7f3k9x2m.domainruntime.cloud published 0.6 s ago
web http://localhost:3000 ready
p publish r restart clients q quit (keeps the environment)Add an entity and an action
Open src/domain.ts:
import { domain, defineAction, i } from "@domainruntime/domain";
import { z } from "zod";
export default domain("app")
.withSchema({
entities: {
app_todo: i.entity({
text: i.string(),
done: i.boolean(),
createdAt: i.date(),
}),
},
links: {},
rooms: {},
})
.withActions({
addTodo: defineAction({
input: z.object({ todoId: z.string().uuid(), text: z.string().trim().min(1).max(200) }),
output: z.object({ todoId: z.string() }),
async execute({ input, runtime }) {
await runtime.tx((tx) =>
tx.app_todo[input.todoId].update({ text: input.text, done: false, createdAt: Date.now() }),
);
return { todoId: input.todoId };
},
}),
});Save the file. druntime publishes it; the action exists in your environment.
Today
runtime.tx is planned. Today the body is:
const db = await runtime.db();
await db.transact(db.tx.app_todo[input.todoId].update({ text: input.text, done: false, createdAt: Date.now() }));
return { todoId: input.todoId };The current scaffold also contains src/runtime.ts, instant.schema.ts and an /api/domain route from the standalone adapter; druntime does not need them.
Read and write from the page
"use client";
import { db } from "@/lib/db";
export default function Page() {
const { isLoading, error, data } = db.useQuery({ app_todo: {} });
if (isLoading) return null;
if (error) return <p>{error.message}</p>;
return (
<main>
<form
onSubmit={async (e) => {
e.preventDefault();
const form = e.currentTarget;
const text = new FormData(form).get("text") as string;
await db.actions.app.addTodo({ todoId: crypto.randomUUID(), text });
form.reset();
}}
>
<input name="text" placeholder="What needs to be done?" />
</form>
<ul>
{data.app_todo.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</main>
);
}import { init } from "@domainruntime/react";
import app from "@/domain";
export const db = init({ domain: app });Open http://localhost:3000, add a todo, then open a second tab: both update live.
Your new entity has no permission rules yet, so anyone with the URL can read it. Add rules before you share the URL.
Change the domain
Add a second action that takes the todo's id and flips done:
toggleTodo: defineAction({
input: z.object({ todoId: z.string().uuid() }),
output: z.object({ done: z.boolean() }),
async execute({ input, runtime }) {
const { app_todo } = await runtime.query({ app_todo: { $: { where: { id: input.todoId } } } });
if (!app_todo[0]) throw new Error("That todo no longer exists");
const done = !app_todo[0].done;
await runtime.tx((tx) => tx.app_todo[input.todoId].update({ done }));
return { done };
},
}),<li key={todo.id} onClick={() => db.actions.app.toggleTodo({ todoId: todo.id })}>
{todo.done ? <s>{todo.text}</s> : todo.text}
</li>Save. The next click runs the new action against the same environment and the same data. Nothing is redeployed or reprovisioned.
Submit an empty todo: the runtime rejects it before your code runs, with action_contract_validation_failed.
What just happened
- The page reads with a live query. Both tabs update because the environment pushes every change to every subscriber.
- The page writes only by calling actions. There is no transaction in the browser; every change is a named operation with validated input.
- The domain file is the backend. Saving it publishes your actions.
Next
- How it works — the model and its guarantees, in one table.
- Modeling data, Reading data, Actions — the three things you just did.
- Guarantees — what is atomic, what runs once, what is live.