ValetAlpha

Quickstart

Zero to running app in 5 minutes.

// App.tsx
import { ValetProvider, useQuery, useMutation } from './_generated/valet/react'
import { api } from './_generated/valet/api'

function App() {
    return (
        <ValetProvider url={__VALET_URL__}>
            <TodoList />
        </ValetProvider>
    )
}

function TodoList() {
    const todos = useQuery(api.todos.list, {})
    const createTodo = useMutation(api.todos.create)

    return (
        <div>
            {todos?.map((todo) => (
                <div key={todo._id}>{todo.title}</div>
            ))}
            <button onClick={() => createTodo({ title: 'New todo' })}>
                Add
            </button>
        </div>
    )
}

This is what you are building toward. Follow the steps below to get there.

Install

bun add valet-dev

Initialize the project

bunx valet-dev init

This creates a valet/ directory with an example schema, a starter auth.ts for built-in email/password auth, and example functions. It also registers a project on the server and saves VALET_PROJECT_URL and VALET_DEPLOY_KEY to .env.

The generated files are starter examples — replace them with your own tables and functions.

If you want to turn on the built-in auth flow right away, set VALET_AUTH_SECRET on the server and add the auth prop to ValetProvider. The starter scaffold already includes authTables and valet/auth.ts.

Configure Vite

// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { valetPlugin } from 'valet-dev/vite'

export default defineConfig({
    plugins: [
        react(),
        valetPlugin({
            local: true,
            enabled: true,
            codegen: {
                // `valet-dev init` scaffolds imports like ../_generated/valet/api
                outputDir: './_generated/valet',
            },
        }),
    ],
})

valetPlugin() makes bun run dev the only command you need in a Vite app. In local mode it starts valet-server, runs codegen, pushes your schema and handlers, and proxies browser requests through Vite at /__valet.

For editor typechecking of the injected defaults, add:

// src/vite-env.d.ts
declare const __VALET_ENABLED__: boolean
declare const __VALET_URL__: string

Define your schema

// valet/schema.ts
import { defineSchema, defineTable, v } from 'valet-dev/server'

export default defineSchema({
    todos: defineTable({
        title: v.string(),
        completed: v.boolean(),
    }).sync({ mode: 'full' }),
})

The schema declares your tables and their validators. Each table gets an automatic _id field.

Write a query and mutation

// valet/todos.ts
import { defineQuery, defineMutation, v } from './_generated/valet/api'

export const list = defineQuery({
    args: {},
    execution: 'local',
    handler: async (ctx) => {
        return ctx.db.query('todos').collect()
    },
})

export const create = defineMutation({
    args: { title: v.string() },
    handler: async (ctx, args) => {
        return ctx.db.insert('todos', {
            title: args.title,
            completed: false,
        })
    },
})

The query uses execution: 'local' so it runs against the client-side SQLite replica -- instant and offline-capable.

Use in React

Wrap your app in ValetProvider and use the generated hooks:

// App.tsx
import { ValetProvider, useQuery, useMutation } from './_generated/valet/react'
import { api } from './_generated/valet/api'

function App() {
    return (
        <ValetProvider url={__VALET_URL__}>
            <TodoList />
        </ValetProvider>
    )
}

function TodoList() {
    const todos = useQuery(api.todos.list, {})
    const createTodo = useMutation(api.todos.create)

    if (todos === undefined) return <div>Loading...</div>

    return (
        <div>
            <ul>
                {todos.map((todo) => (
                    <li key={todo._id}>{todo.title}</li>
                ))}
            </ul>
            <button onClick={() => createTodo({ title: 'New todo' })}>
                Add Todo
            </button>
        </div>
    )
}

Run the app

bun run dev

With the Vite plugin in place, this starts the dev server, boots a local Valet server, keeps _generated/valet/ up to date, and proxies the browser client through /__valet.

If you are not using Vite, the lower-level CLI still works:

bunx valet-dev codegen --watch

Next steps

  • Schema -- Tables, validators, indexes, and the _id field.
  • Queries -- Execution modes, filters, ordering, and pagination.
  • Actions -- Server-only functions, fetch, and ctx.invoker.
  • Cron -- Schedule actions and manually trigger them in development.
  • Messaging -- Transactional email delivery through the outbox.
  • Sync Rules -- Control which documents sync to which users.
  • Auth -- JWT tokens and ctx.auth.

On this page