Database Quick Start
Varity includes a zero-config database. Import db from the SDK and start querying immediately — no database setup, no connection strings, no ORM configuration.
Installation
Section titled “Installation”The database module is included in @varity-labs/sdk. No additional packages needed.
npm install @varity-labs/sdkDefine Your Data Types
Section titled “Define Your Data Types”Start by defining TypeScript interfaces for your data:
export interface Project { id?: string; name: string; description: string; status: 'active' | 'paused' | 'completed'; owner: string; members: string[]; dueDate: string; createdAt: string;}
export interface Task { id?: string; projectId: string; title: string; description?: string; status: 'todo' | 'in_progress' | 'done'; priority: 'low' | 'medium' | 'high'; assignee?: string; dueDate?: string; createdAt: string;}Create Collection Helpers
Section titled “Create Collection Helpers”Create a file that exports typed collection accessors:
import { db } from '@varity-labs/sdk';import type { Project, Task } from '../types';
export const projects = () => db.collection<Project>('projects');export const tasks = () => db.collection<Task>('tasks');Each call to db.collection<T>('name') returns a typed collection with full CRUD operations.
Basic Operations
Section titled “Basic Operations”Insert Documents
Section titled “Insert Documents”import { projects } from './database';
// Insert a single documentconst newProject = await projects().add({ name: 'My Project', description: 'A new project', status: 'active', owner: 'user@example.com', members: ['user@example.com'], dueDate: '2026-03-01', createdAt: new Date().toISOString(),});
// Expected output:// {// id: "550e8400-e29b-41d4-a716-446655440000", // Auto-generated ID// name: "My Project",// description: "A new project",// status: "active",// owner: "user@example.com",// members: ["user@example.com"],// dueDate: "2026-03-01",// createdAt: "2026-03-05T14:23:11.000Z"// }Query Documents
Section titled “Query Documents”// Get all documents in a collectionconst allProjects = await projects().get();// Returns: Array of all project documents
// Get with pagination (useful for large datasets)const page1 = await projects().get({ limit: 10, offset: 0 }); // First 10 itemsconst page2 = await projects().get({ limit: 10, offset: 10 }); // Next 10 items
// Get with ordering (prefix with - for descending)const newest = await projects().get({ orderBy: '-createdAt' });// Returns: Projects sorted by creation date, newest firstFiltering: The .get() method returns all documents. Filter on the client side:
// Fetch all projects firstconst allProjects = await projects().get();
// Filter by statusconst activeProjects = allProjects.filter(p => p.status === 'active');// Returns: Only projects with status === 'active'
// Filter tasks by project ID (relational filtering)const projectTasks = (await tasks().get()).filter(t => t.projectId === 'proj-123');// Returns: Only tasks belonging to project 'proj-123'Update Documents
Section titled “Update Documents”// Update by IDawait projects().update('proj-123', { status: 'completed',});
// Update specific fields (partial update)await projects().update('proj-123', { name: 'Updated Name', description: 'New description',});Delete Documents
Section titled “Delete Documents”// Delete by IDawait projects().delete('proj-123');Build a React Hook
Section titled “Build a React Hook”The recommended pattern is to wrap database operations in a custom React hook. This provides loading states, error handling, and optimistic UI updates:
'use client';
import { useState, useEffect, useCallback } from 'react';import { projects } from './database';import type { Project } from '../types';
export function useProjects() { const [data, setData] = useState<Project[]>([]); const [loading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null);
// Load all projects on mount const refresh = useCallback(async () => { try { setLoading(true); setError(null); const result = await projects().get(); setData(result as Project[]); } catch (err) { // Proper error handling with fallback message setError(err instanceof Error ? err.message : 'Failed to load'); console.error('Failed to load projects:', err); } finally { // Always set loading to false, even on error setLoading(false); } }, []);
useEffect(() => { refresh(); }, [refresh]);
// Create with optimistic UI (instantly show new item before server confirms) const create = async (input: Omit<Project, 'id' | 'createdAt'>) => { // Generate temporary ID for optimistic update const optimistic: Project = { ...input, id: `temp-${Date.now()}`, createdAt: new Date().toISOString(), }; // Add to UI immediately setData(prev => [optimistic, ...prev]);
try { // Save to database await projects().add({ ...input, createdAt: optimistic.createdAt }); // Refresh to get real ID from server await refresh(); } catch (err) { // Rollback on failure - remove the optimistic item setData(prev => prev.filter(p => p.id !== optimistic.id)); console.error('Failed to create project:', err); throw err; // Re-throw so UI can show error } };
// Update with optimistic UI const update = async (id: string, updates: Partial<Project>) => { const original = data.find(p => p.id === id); setData(prev => prev.map(p => p.id === id ? { ...p, ...updates } : p));
try { await projects().update(id, updates); } catch (err) { if (original) setData(prev => prev.map(p => p.id === id ? original : p)); throw err; } };
// Delete with optimistic UI const remove = async (id: string) => { const original = data.find(p => p.id === id); setData(prev => prev.filter(p => p.id !== id));
try { await projects().delete(id); } catch (err) { if (original) setData(prev => [...prev, original]); throw err; } };
return { data, loading, error, create, update, remove, refresh };}Use the Hook in a Component
Section titled “Use the Hook in a Component”'use client';
import { useProjects } from '../../../lib/hooks';
export default function ProjectsPage() { const { data: projects, loading, error, create, remove } = useProjects();
if (loading) return <p>Loading projects...</p>; if (error) return <p>Error: {error}</p>;
return ( <div> <h1>Projects ({projects.length})</h1>
<button onClick={() => create({ name: 'New Project', description: 'Created just now', status: 'active', owner: 'me@example.com', members: ['me@example.com'], dueDate: '2026-12-31', })}> Add Project </button>
{projects.map(project => ( <div key={project.id}> <h3>{project.name}</h3> <p>{project.description}</p> <span>Status: {project.status}</span> <button onClick={() => remove(project.id!)}>Delete</button> </div> ))} </div> );}Filtering with Related Data
Section titled “Filtering with Related Data”To filter documents based on a parent relationship (e.g., tasks for a specific project):
export function useTasks(projectId?: string) { const [allTasks, setAllTasks] = useState<Task[]>([]); const [loading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null);
const refresh = useCallback(async () => { try { setLoading(true); const result = await tasks().get(); setAllTasks(result as Task[]); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to load'); } finally { setLoading(false); } }, []);
useEffect(() => { refresh(); }, [refresh]);
// Client-side filtering const data = projectId ? allTasks.filter(t => t.projectId === projectId) : allTasks;
// ... create, update, remove (same pattern as above)
return { data, loading, error, refresh };}Environment Variables
Section titled “Environment Variables”NEXT_PUBLIC_VARITY_APP_ID=your-app-idNEXT_PUBLIC_VARITY_APP_TOKEN=your-app-tokenNEXT_PUBLIC_VARITY_DB_PROXY_URL=your-db-urlVITE_VARITY_APP_ID=your-app-idVITE_VARITY_APP_TOKEN=your-app-tokenVITE_VARITY_DB_PROXY_URL=your-db-urlThe Pattern: Collection, Hook, Page
Section titled “The Pattern: Collection, Hook, Page”Every feature in a Varity app follows the same 3-step pattern:
- Type — Define a TypeScript interface in
types/index.ts - Collection — Create a typed collection accessor in
database.ts - Hook — Build a React hook with CRUD + optimistic updates
- Page — Use the hook in a page component
This pattern is used throughout the SaaS Starter Template for projects, tasks, and team members.
Next Steps
Section titled “Next Steps”- Authentication — Add user login to protect data
- SaaS Starter Template — See a full working app with database integration
- Add a CRUD Feature — Step-by-step guide to add a new data model
- Deploy — Go live