Database schema, security, and data management with Supabase or Convex
Shipnative supports two backends: Supabase (PostgreSQL) and Convex (reactive TypeScript). This guide covers schema design, security patterns, and data management for both.
Database vs Local Storage: Store user data (profiles, preferences, content) in the database so it syncs across devices. Use MMKV only for local caching. See State Management for details.
-- Users can only access their own dataCREATE POLICY "Users access own data" ON profiles FOR ALL USING (auth.uid() = id);-- Public read, authenticated writeCREATE POLICY "Anyone can read posts" ON posts FOR SELECT USING (true);CREATE POLICY "Users create own posts" ON posts FOR INSERT WITH CHECK (auth.uid() = author_id);
Always enable RLS: When creating new tables, run ALTER TABLE name ENABLE ROW LEVEL SECURITY; and add policies.
Convex uses function-level security. The boilerplate includes security helpers in convex/lib/security.ts:
No Database-Level RLS: Unlike Supabase, Convex has no automatic row-level security. You must use these helpers in every function that accesses user data.
Shipnative uses database migrations to version control schema changes. Always use migrations instead of editing schema.sql directly.Migrations provide version control and prevent “already exists” errors when running the schema multiple times—the industry standard used by Rails, Django, Laravel, and Prisma.
Always use IF NOT EXISTS - Makes migrations safe to run multiple times
Drop policies before recreating - Prevents “already exists” errors
Enable RLS on all user tables - Security best practice
Add indexes for foreign keys - Performance optimization
Example migration:
-- Create tableCREATE TABLE IF NOT EXISTS public.todos ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL, title TEXT NOT NULL, completed BOOLEAN DEFAULT false, created_at TIMESTAMPTZ DEFAULT NOW());-- Enable RLSALTER TABLE public.todos ENABLE ROW LEVEL SECURITY;-- Add policiesDROP POLICY IF EXISTS "Users can view own todos" ON public.todos;CREATE POLICY "Users can view own todos" ON public.todos FOR SELECT USING (auth.uid() = user_id);-- Add indexesCREATE INDEX IF NOT EXISTS todos_user_id_idx ON public.todos(user_id);
See the complete migration guide at supabase/migrations/README.md in your project.
The boilerplate includes a DataDemoScreen that demonstrates proper data fetching patterns for your chosen backend. Use it as a template when building your own data-driven screens.
Navigate to the DataDemoScreen from any authenticated screen:
navigation.navigate('DataDemo')
Copy the patterns, not the screen. The DataDemoScreen is meant to be a reference. When building your own screens, look at the version matching your backend (.supabase.tsx or .convex.tsx) and copy the data fetching patterns.