Skip to content

Architecture

Franciszek Ryszka edited this page Aug 10, 2026 · 3 revisions

Architecture

SnipVault is a single frontend that runs in two environments — a Next.js web server and a Tauri desktop shell — sharing all UI code but swapping the storage backend.

The dual-runtime bridge

The key abstraction is lib/tauri-api.ts. It exposes the same functions — getSnippets, createSnippet, updateSnippet, deleteSnippet — and picks a backend at runtime:

export function isTauri(): boolean {
  return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
}
  • In Tauri, it calls Rust via invoke("get_snippets", …).
  • In the browser, it calls the REST API via fetch("/api/snippets").

The React components never know which one they're talking to.

                       ┌─────────────────────────────┐
                       │   React UI (components/)     │
                       │   Snippet form, search, …    │
                       └──────────────┬──────────────┘
                                      │
                          lib/tauri-api.ts (isTauri?)
                          ┌───────────┴───────────┐
                   browser│                       │Tauri
                          ▼                       ▼
              app/api/snippets/*          src-tauri (#[tauri::command])
                 (Next.js routes)              lib.rs → db.rs
                          │                       │
                 better-sqlite3               rusqlite
                          ▼                       ▼
               ./data/snippets.db      <local data dir>/snipvault/snippets.db

Web runtime

  • Routes: app/api/snippets/* (GET list, POST create, PUT/PATCH/DELETE by id, /copy, /restore), plus app/api/health and app/api/sync for the sync feature. A root proxy.ts (Next 16 proxy convention) gates all /api/* behind a bearer token when SNIPVAULT_TOKEN is set.
  • Storage: lib/db.ts opens a better-sqlite3 database at ./data/snippets.db (lazily, on first query — so next build never races to open it), enables WAL mode, and creates/migrates the schema.

Desktop runtime

  • Commands: src-tauri/src/lib.rs registers the snippet, database-management, and sync #[tauri::command]s (see API and Commands) and holds a Mutex<AppState> with the DB handle.
  • Storage: src-tauri/src/db.rs uses rusqlite, storing the DB under the OS local data dir (dirs::data_local_dir()/snipvault/snippets.db), WAL mode, same schema.

Syncing (added in v2.2.0)

Both runtimes are local-first: snippet reads and writes always hit the local database. On top of that, the desktop app can reconcile with a self-hosted server — which is just the SnipVault web app, containerized. A sync is one POST /api/sync round trip (via the Tauri HTTP plugin, so CSP/CORS don't apply): the client pushes its records, the server merges them by uuid with newest-updated_at-wins, and returns its merged set for the client to apply the same way. Deletes travel as tombstones. There's no live/remote data path — the server is only contacted during a sync. See Syncing and Data Storage.

Build-time reconciliation

Because Next.js static export (output: "export") can't coexist with app/api route handlers, the desktop build temporarily removes app/api during export. See Building and Packaging for the mechanics of scripts/build-tauri.mjs.

Tech stack

Area Technology
Framework Next.js 16 (App Router, Turbopack), React 19
Desktop shell Tauri 2 (Rust)
UI components shadcn/ui + Radix UI
Styling Tailwind CSS 3
Forms/validation React Hook Form + Zod
Syntax highlighting highlight.js
Theming next-themes
Web DB better-sqlite3
Desktop DB rusqlite (+ serde, chrono, dirs, uuid)
Sync transport @tauri-apps/plugin-http (desktop) → server /api/sync
Secret storage keyring crate — OS credential store for the sync token (desktop, v2.6.0)
External links @tauri-apps/plugin-opener — "Run in…" launcher opens URLs in the system browser (desktop, v2.15.0)

Consistency notes

The web and desktop backends implement the same query logic (language/tag/search-mode filters, and the pinned-first sort orders — newest / most-used / recently-used / A–Z, from a shared allow-list) and the same schema, so data and behavior are portable between them. The Snippet shape is mirrored in TypeScript (lib/tauri-api.ts, lib/db.ts) and Rust (db.rs). See Data Storage and API and Commands.

Clone this wiki locally