Skip to content

Quick Start

This guide walks you through setting up zod-vault in a React application.

Terminal window
npm install @zod-vault/zustand @zod-vault/client @zod-vault/crypto
import { generateRecoveryKey } from "@zod-vault/crypto";
const recoveryKey = generateRecoveryKey();
console.log(recoveryKey);
// => "ABCD-EFGH-IJKL-MNOP-QRST-UVWX-YZ23-4567-ABCD-EFGH-IJKL-MNOP-Q"

Store this key safely. It’s the only way to decrypt your data.

lib/vault.ts
import { VaultClient } from "@zod-vault/client";
export const vaultClient = new VaultClient({
serverUrl: "https://vault.example.com",
});
stores/notes.ts
import { create } from "zustand";
import { vault } from "@zod-vault/zustand";
import { vaultClient } from "../lib/vault";
interface NotesState {
notes: string[];
addNote: (note: string) => void;
removeNote: (index: number) => void;
}
export const useNotes = create(
vault<NotesState>(
(set) => ({
notes: [],
addNote: (note) => set((s) => ({ notes: [...s.notes, note] })),
removeNote: (index) =>
set((s) => ({ notes: s.notes.filter((_, i) => i !== index) })),
}),
{
name: "notes",
recoveryKey: process.env.NEXT_PUBLIC_RECOVERY_KEY!,
server: "https://vault.example.com",
getToken: () => vaultClient.getToken(),
}
)
);
components/AuthGate.tsx
import { useState } from "react";
import { useVaultAuth } from "@zod-vault/client";
import { vaultClient } from "../lib/vault";
export function AuthGate({ children }: { children: React.ReactNode }) {
const { isAuthenticated, isLoading } = useVaultAuth(vaultClient);
if (isLoading) return <div>Loading...</div>;
if (!isAuthenticated) return <LoginForm />;
return <>{children}</>;
}
function LoginForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const { login } = useVaultAuth(vaultClient);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await login(email, password);
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
/>
<button type="submit">Login</button>
</form>
);
}
components/Notes.tsx
import { useState } from "react";
import { useNotes } from "../stores/notes";
export function Notes() {
const { notes, addNote, removeNote } = useNotes();
const [input, setInput] = useState("");
return (
<div>
<ul>
{notes.map((note, i) => (
<li key={i}>
{note}
<button onClick={() => removeNote(i)}>Delete</button>
</li>
))}
</ul>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="New note"
/>
<button onClick={() => { addNote(input); setInput(""); }}>
Add
</button>
</div>
);
}

The store auto-syncs every 30 seconds. For manual control:

// Full sync (push + pull)
await useNotes.vault.sync();
// Or granular
await useNotes.vault.push(); // Local → Server
await useNotes.vault.pull(); // Server → Local
// Check status
useNotes.vault.getSyncStatus();
// => "idle" | "syncing" | "synced" | "error" | "offline"

Don’t need cloud sync? Skip the server:

const useNotes = create(
vault<NotesState>(
(set) => ({ ... }),
{
name: "notes",
recoveryKey: "your-recovery-key",
// No server = encrypted localStorage only
}
)
);