quickstart
from nothing to a live reactive query in five minutes.
- 1
create a project in the dashboard
Sign in at briven.tech, click new project, copy the resulting project id (looks likep_01HZ…).dashboard-only path:if you want to avoid the CLI entirely, open the project's studio tab and click + new table — define columns, set PKs/FKs/indexes, then insert rows and run queries from the SQL editor. The copy as schema.tsbutton graduates you to CLI + git when you're ready. The steps below describe the CLI flow. - 2
generate an api key
Open api keys on the new project and create one — pick thedeveloperrole for local iteration. The plaintext (brk_…) is shown once; store it in a secret manager immediately. - 3
scaffold locally
$ mkdir my-app && cd my-app $ npm install @briven/cli @briven/schema $ npx briven init $ npx briven login --project p_xxx --key brk_xxx
briven initdrops three files:briven.config.ts,briven/schema.ts, andbriven/functions/notes.ts. The default template is a minimal notes app you can deploy as-is. Usebriven init --template=todo-appor--template=chatfor richer starting points. - 4
edit the schema + function
Open
briven/schema.ts:import { schema, table, text, timestamp } from '@briven/schema'; export default schema({ notes: table({ columns: { id: text().primaryKey(), body: text().notNull(), createdAt: timestamp().notNull().defaultNow(), }, indexes: [{ columns: ['createdAt'] }], }), });Open
briven/functions/notes.ts:import { mutation, query } from '@briven/cli/server'; export const list = query({ args: {}, handler: async (ctx) => { return ctx.db('notes') .select(['id', 'body', 'createdAt']) .orderBy('createdAt', 'desc') .limit(100); }, }); export const create = mutation({ args: { body: 'string' }, handler: async (ctx, args) => { const id = crypto.randomUUID(); await ctx.db('notes').insert({ id, body: args.body, createdAt: new Date(), }); return { id }; }, }); - 5
deploy
$ npx briven deploy
The CLI prints a schema diff, applies it transactionally on the data plane, uploads the function bundle, and signals the runtime to swap in the new isolate. New row appears under deployments on the dashboard.
- 6
wire it up from your app
$ npm install @briven/react
In your app (Next.js, Vite, Remix — anything React):
// app/providers.tsx 'use client'; import { BrivenProvider, createClient } from '@briven/react'; const client = createClient({ projectId: 'p_01HZ...', apiKey: process.env.NEXT_PUBLIC_BRIVEN_PUBLIC_KEY!, // 'brk_pub_...' }); export function Providers({ children }: { children: React.ReactNode }) { return <BrivenProvider client={client}>{children}</BrivenProvider>; } // app/notes/page.tsx 'use client'; import { useMutation, useQuery } from '@briven/react'; export default function Notes() { const { data, isLoading } = useQuery('notes:list', {}); const create = useMutation('notes:create'); if (isLoading) return <p>loading…</p>; return ( <div> {data?.map(n => <p key={n.id}>{n.body}</p>)} <button onClick={() => create.mutate({ body: 'hello' })}>add</button> </div> ); }On add, briven inserts the row, the realtime service picks up the postgres NOTIFY for the
notestable, re-invokes every activenotes:listsubscription, and pushes the fresh result — no polling, no manual cache invalidation.
what just happened
briven deploycompiled your TypeScript schema to a postgres migration and ran it transactionally — if any column add failed, no rows changed.- Your function bundle was uploaded to the runtime and assigned a fresh Deno isolate. Cold start budget: 200ms p50.
- On every
useQueryon the client, briven's realtime service registered a LISTEN on every postgres table the function read — so any subsequent mutation that touches those tables triggers an automatic re-invoke.
what to read next
schema
every column type, constraint, index, default, and diff semantic
functions
query/mutation/action wrappers, Ctx, db builder, lifecycle, error handling
cli
every command — deploy, dev (watch + hot reload), env, db, logs, invoke, projects
migration
moving from convex, supabase, postgres/drizzle/prisma, firebase, hasura, nextauth