DocsGet StartedQuickstart

Quickstart

Build your first local-first application with TopGun in under 5 minutes. No server required to start — the app works offline by default and syncs when you add one. Want to see it running first? Try the live demo.

1. Installation

Install the core client, adapters, the React hooks package, and Zod for schema definitions.

terminal
npm install @topgunbuild/client @topgunbuild/adapters @topgunbuild/react zod

2. The Canonical App

Hook-first is the simple way to read and write data with React. One read hook (useQuery) gives you the list and re-renders when data changes. One write hook (useMutation) adds new items. One type-safe Todo shape ties them together. The whole loop fits in 24 lines, and your list shows up instantly then keeps itself in sync as data changes.

src/app.tsx
import { useQuery, useMutation, TopGunProvider } from '@topgunbuild/react';
import { TopGunClient } from '@topgunbuild/client';
import { IDBAdapter } from '@topgunbuild/adapters';
import { type Todo } from './schema';

export const client = new TopGunClient({
  storage: new IDBAdapter(),
  // Uncomment to connect to a server:
  // serverUrl: 'ws://localhost:8080',
});

function TodoApp() {
  const { data: todos = [] } = useQuery<Todo>('todos');
  const { create } = useMutation<Todo>('todos');
  const add = () => create(`todo-${Date.now()}`, { text: 'Ship v2', done: false });
  return (
    <>
      <button onClick={add}>Add</button>
      <ul>{todos.map(t => <li key={t._key}>{t.text}</li>)}</ul>
    </>
  );
}

export default () => <TopGunProvider client={client}><TodoApp /></TopGunProvider>;
src/schema.ts
import { z } from 'zod';
export const TodoSchema = z.object({ text: z.string(), done: z.boolean() });
export type Todo = z.infer<typeof TodoSchema>;

3. How It Works

Here is a step-by-step breakdown of the canonical snippet above:

Lines 1–4: Imports. Three packages installed in step 1 (@topgunbuild/react, @topgunbuild/client, @topgunbuild/adapters) plus the Todo type derived from your schema.

Lines 6–9: Client init. TopGunClient takes a storage adapter for local persistence. IDBAdapter writes to IndexedDB so your data survives page reloads. No explicit start() call is needed in module-level code — the adapter initializes lazily in the background. Uncomment serverUrl to connect to a sync server.

Line 12: Read hook. useQuery<Todo>('todos') subscribes to the todos map. The component re-renders automatically whenever data changes locally or arrives from the server.

Line 13: Write hook. useMutation<Todo>('todos') returns { create, update, remove }. Call create(key, value) to add a new item. The write lands in memory immediately (zero-latency) and syncs to the server in the background when a server is configured.

Line 14: Add function. A plain function that calls create with a timestamp-based key and the new todo shape. No loading state, no async/await in the component — the write is always instant.

Lines 15–20: Render. Standard React JSX. todos.map(t => ...) iterates the live list. Use t._key as the React key — this is the string key you passed to create, exposed on every query result item.

Line 23: Provider. TopGunProvider makes the client available to all hooks in the tree via React context.

4. End-to-End Types

No manual interface needed — the type flows from the Zod schema through the hook generic:

// schema.ts
export const TodoSchema = z.object({ text: z.string(), done: z.boolean() });
export type Todo = z.infer<typeof TodoSchema>;
// app.tsx
import { type Todo } from './schema';
const { data: todos = [] } = useQuery<Todo>('todos');

z.infer<typeof TodoSchema> produces { text: string; done: boolean }. Pass that as the generic to useQuery<Todo> and TypeScript knows the shape of every item in todos — autocomplete works, typos are caught at compile time, and you never have to keep a manual interface in sync with your schema.

5. Imperative API (advanced)

Use when outside React — service workers, Node scripts, non-component utilities, or when you need direct map access without hooks.

example.ts
const todos = client.getMap('todos');
todos.set(`todo-${Date.now()}`, { text: 'Ship v2', done: false });

client.getMap('todos') returns the map directly. .set(key, value) writes to local memory immediately and queues the change for sync. This is not the recommended path for UI code — prefer useQuery and useMutation in React components.

Non-Blocking Initialization

IndexedDB can take 50-500ms to initialize. TopGun’s IDBAdapter initializes lazily in the background and queues reads and writes until the store is ready — your UI renders instantly with zero blocking time and no await ceremony. If you need a hard signal that persistence is ready (e.g., before a critical migration step), keep a reference to the adapter and call await adapter.waitForReady().

Persistence

By using IDBAdapter, your data is automatically saved to IndexedDB. Even if the user refreshes the page or closes the browser, the state is preserved locally.

Optional: Encrypt Local Storage

For sensitive data, wrap your adapter with EncryptedStorageAdapter to encrypt data at rest. See the Client-Side Encryption section in the Security Guide.

Building with an AI Agent?

See the AI Builder guide for prompt templates, agent setup, and live database access via MCP.

6. Add real-time sync (optional)

By default the app runs in local-only mode — data persists in IndexedDB and works offline without a server. To add real-time sync across browsers and devices:

  1. Uncomment the serverUrl line in the client init above.
  2. Start the TopGun server. Two paths:

Using the CLI (recommended — works from any directory once the server binary is present):

# Install the unified TopGun developer CLI
npm install -g @topgunbuild/cli

# Check your environment
topgun doctor

# Interactive project setup — writes .env with zero-config defaults
# (TOPGUN_NO_AUTH=1 for local dev, STORAGE_BACKEND=redb for embedded storage)
topgun setup

# Start the development server
topgun dev

The setup→dev flow is cohesive: topgun setup writes a .env that topgun dev reads to start the server without any JWT or database configuration. Run topgun setup --yes to skip prompts and accept all defaults.

Zero-install path (no Rust toolchain required):

npx @topgunbuild/server

This downloads a prebuilt binary for your platform and starts the server immediately. No compiler, no Docker.

Inside the TopGun monorepo (contributor path):

pnpm start:server

pnpm start:server uses a prebuilt binary when present and falls back to cargo run --bin topgun-server --release for contributors who have the Rust toolchain.

The server boots in seconds with embedded storage (./topgun.redb) — no Postgres, no Docker required.

  1. Open your app in two browser tabs and watch changes sync in real time.

In production, always use wss:// instead of ws:// to encrypt data in transit. See the Security Guide for TLS configuration.

7. Explore your data visually

The admin dashboard gives you a live visual map of your data — Maps, Explorer, Cluster topology, Query Playground, and Prometheus metrics — all connected to your local server over WebSocket.

Zero-install path (no monorepo, no Rust toolchain required):

npx @topgunbuild/server

The prebuilt server ships with the admin dashboard bundled in. Open http://localhost:8080/admin/ in your browser — the SPA is served directly by the server, no extra process or build step. When the server is running in zero-config mode (TOPGUN_NO_AUTH=1, the npx default), the dashboard opens straight to the Dashboard screen — no login credentials required.

Inside the TopGun monorepo (contributor path with live reload):

topgun dev --admin

This launches the Vite dev server alongside the Rust server for hot-reload while you work on the dashboard. Open http://localhost:5173/admin/.

Custom build? Point TOPGUN_ADMIN_DIR at your own admin build directory to override the bundled SPA. Leave it unset to serve the bundled dashboard (npm) or the monorepo build (./admin-dashboard/dist).

Other ways to reach the admin: