Quickstart
Build a frontend + backend Todo app in 5 minutes
Let's build a Todo app with authentication, CRUD operations, and realtime updates — from scratch in 5 minutes.
1. Create the Project
npx gencow init my-todo --template task-app
cd my-todo && bun install2. Explore the Schema
Open gencow/schema.ts — the template already includes a tasks table:
import { pgTable, serial, text, boolean, timestamp } from "drizzle-orm/pg-core";
import { ownerRls } from "@gencow/core";
import { user } from "./generated/auth-schema";
export const tasks = pgTable("tasks", {
id: serial("id").primaryKey(),
title: text("title").notNull(),
done: boolean("done").default(false).notNull(),
userId: text("user_id")
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
createdAt: timestamp("created_at").defaultNow().notNull(),
}, (t) => [ownerRls(t)]);Security:
pgTablewithownerRls()applies PostgreSQL Row-Level Security to automatically isolate data per user. Combined withcreateCrud(), every operation enforces authentication and data ownership — no manual filtering needed.⚠️ Schema Rule: Every table using
createCrud()must have anidcolumn. The template already includes this.
3. Auto-Generated CRUD API
Open gencow/tasks.ts — a single line generates the entire API:
import { createCrud } from "./runtime";
import { tasks } from "./schema";
// ✅ One line → list, get, create, update, remove with auth + realtime
export const { list, get, create, update, remove } = createCrud(tasks);createCrud(tasks) auto-generates:
- Read procedures:
tasks.list→ returns{ data: Task[], total: number },tasks.get→ returns single task by id - Write procedures:
tasks.create,tasks.update,tasks.remove— all with auth + realtime push - Security: Authentication required by default. Use
createCrud(tasks, { allowAnonymous: true })for public access. - ID auto-detection:
serial→ number,text/uuid→ string (automatic)
Need custom logic? Add
procedure.query/procedure.mutationalongsidecreateCrud()— see Queries Guide and Mutations Guide.
4. Register API Definitions
Open gencow/index.ts — import the generated procedure definitions and register them with defineApi:
import "./runtime";
import { defineApi } from "@gencow/core";
import {
list as tasksList,
get as tasksGet,
create as tasksCreate,
update as tasksUpdate,
remove as tasksRemove,
} from "./tasks";
export default defineApi({
procedures: {
tasksList,
tasksGet,
tasksCreate,
tasksUpdate,
tasksRemove,
},
});defineApi({ procedures }) is the current registration source of truth. Do not use namespace re-exports or defineApi({ crud }) in new app code.
5. Start the Dev Server
gencow devYou'll see:
Gencow Dev
▸ Functions: ./gencow
▸ Storage: ./.gencow/uploads
▸ DB: Cloud PostgreSQL
✓ Schema → migrations synced
✓ Generated gencow/api.ts
✓ Generated gencow/README.md (AI vibe-coding guide)
Starting server with hot-reload...
✓ Server running at http://localhost:5456
✓ Dashboard: http://localhost:5456/_admin
✓ Schema → migrations synced—gencow devautomatically runsnpx drizzle-kit generateto create SQL migration files ingencow/migrations/.gencow deploy(production) also auto-runs this before bundling — soschema.tschanges are always captured without a manual step.
Verify the API
Open the admin dashboard at http://localhost:5456/_admin to:
- Browse your database tables
- View registered queries and mutations
- Test API endpoints
6. Connect a Vite + React Frontend
For a new UI, use Vite + React conventions: src/main.tsx as the entry point, src/App.tsx for the app shell, and import.meta.env.VITE_API_URL for the backend URL. Do not choose Next.js unless the user explicitly asks for Next.js/SSR or you are working inside an existing Next.js project.
Install the React SDK:
npm install @gencow/client @gencow/reactSet Up Auth
// src/lib/auth.ts
import { createAuthClient } from "@gencow/react";
export const auth = createAuthClient();
export const { signIn, signUp, signOut, useAuth } = auth;Set Up the Provider
// src/main.tsx
import { GencowProvider } from "@gencow/react";
import { auth } from "./lib/auth";
function AppWrapper({ children }) {
// VITE_API_URL is auto-set by `gencow init` and `gencow dev`
const baseUrl = import.meta.env.VITE_API_URL;
return (
<GencowProvider baseUrl={baseUrl} auth={auth}>
{children}
</GencowProvider>
);
}Build the Todo UI
import { useQuery, useMutation } from "@gencow/react";
import { api } from "../gencow/api"; // auto-generated
function TodoApp() {
// createCrud().list returns { data: Task[], total: number }
const result = useQuery(api.tasks.list);
const { mutate: createTask, isPending: isCreating } = useMutation(api.tasks.create);
const { mutate: updateTask } = useMutation(api.tasks.update);
const { mutate: removeTask } = useMutation(api.tasks.remove);
if (result === undefined) return <div>Loading...</div>;
return (
<div>
<h1>My Todos ({result.total})</h1>
{/* Create form */}
<form onSubmit={(e) => {
e.preventDefault();
const title = new FormData(e.currentTarget).get("title") as string;
createTask({ title });
e.currentTarget.reset();
}}>
<input name="title" placeholder="New task..." required />
<button disabled={isCreating}>
{isCreating ? "Adding..." : "Add"}
</button>
</form>
{/* Task list — use result.data (not result directly) */}
<ul>
{result.data.map((t) => (
<li key={t.id}>
<span
onClick={() => updateTask({ id: t.id, done: !t.done })}
style={{ cursor: "pointer" }}
>
{t.done ? "✅" : "⬜"} {t.title}
</span>
<button onClick={() => removeTask({ id: t.id })}>🗑️</button>
</li>
))}
</ul>
</div>
);
}Realtime magic: When you call
createTask(), the server automatically pushes the updated data via WebSocket. AlluseQuery(api.tasks.list)hooks refresh instantly — no manual refetching needed!
7. Add Seed Data (Optional)
Create gencow/seed.ts to populate the database with test data:
export default async function seed(ctx) {
// Create a test user first (auth is handled separately)
// Then seed tasks
await ctx.db.insert(tasks).values([
{ title: "Learn Gencow", userId: "test-user-id" },
{ title: "Build an awesome app", userId: "test-user-id" },
{ title: "Deploy to cloud", userId: "test-user-id" },
]);
}# Run seed (server must be running)
gencow db:seed8. Deploy
# Login (first time only)
gencow login
# Start dev (real-time backend deployment)
gencow dev
# Deploy frontend (if you have one)
VITE_API_URL=https://<app-id>.gencow.app npm run build
gencow static dist/That's it! Your app is live on https://<app-id>.gencow.app. 🎉
gencow devwatches for file changes and auto-deploys.gencow staticdeploys your built frontend.For production deployment (Pro+ only), use
gencow deploy --prod.
Next Steps
- Project Structure — Understand every config file
- Schema Guide — Advanced schema patterns
- Authentication — Auth setup in detail
- AI Engine — Add AI to your app
- Deployment — Cloud deployment details