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-devInitialize the project
bunx valet-dev initThis 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__: stringDefine 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 devWith 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 --watchNext steps
- Schema -- Tables, validators, indexes, and the
_idfield. - Queries -- Execution modes, filters, ordering, and pagination.
- Actions -- Server-only functions,
fetch, andctx.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.