Skip to main content
Shipnative supports two backends: Supabase (PostgreSQL) and Convex (reactive TypeScript). This guide covers schema design, security patterns, and data management for both.

Database Schema

When you run supabase/schema.sql, it creates core tables for common app requirements.

Core Tables

TableDescriptionKey Fields
profilesUser profile informationfirst_name, last_name, avatar_url, bio
user_preferencesApp-specific settingslanguage, timezone, profile_visibility
push_tokensDevice tokens for notificationstoken, platform, device_id, is_active
waitlistMarketing waitlistemail, source, created_at
Automatic Sync: The profiles and user_preferences tables are automatically created via triggers whenever a new user signs up.

Security

Security is built-in at the database level with Row Level Security (RLS):
  • Strict Privacy: Users can only access their own data
  • Public Profiles: Basic profile info is publicly readable for social features
  • Tokens & Preferences: Strictly private, owner-only access
-- Users can only access their own data
CREATE POLICY "Users access own data"
  ON profiles FOR ALL
  USING (auth.uid() = id);

-- Public read, authenticated write
CREATE 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.

Managing the Schema

Adding New Tables

  1. Design the schema: Use the Database Prompts
  2. Apply SQL: Run CREATE TABLE and RLS statements in the SQL Editor
  3. Update Types: Update TypeScript interfaces to match

Modifying Tables

ALTER TABLE public.[table_name] ADD COLUMN [column_name] [type] DEFAULT [value];

Seed Data

Use the SQL Editor to insert test data:
INSERT INTO profiles (id, first_name, last_name)
VALUES ('user-id-here', 'Demo', 'User');
Or import from a file:
npx supabase db seed

Reference Implementation: DataDemoScreen

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.

How It Works

The screen uses conditional exports to load the correct implementation:
screens/
├── DataDemoScreen.tsx           # Router - loads correct version
├── DataDemoScreen.supabase.tsx  # Supabase patterns
└── DataDemoScreen.convex.tsx    # Convex patterns
The main DataDemoScreen.tsx automatically exports the right version based on your EXPO_PUBLIC_BACKEND_PROVIDER setting:
import { isConvex } from "@/config/env"

export const DataDemoScreen = isConvex
  ? require("./DataDemoScreen.convex").DataDemoScreen
  : require("./DataDemoScreen.supabase").DataDemoScreen

What Each Version Demonstrates

File: screens/DataDemoScreen.supabase.tsxShows the standard React Query + Supabase SDK pattern:
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import { supabase } from "@/services/supabase"

// Fetching data
const usePosts = () => {
  return useQuery({
    queryKey: ["posts"],
    queryFn: async () => {
      const { data, error } = await supabase
        .from("posts")
        .select("*")
        .order("created_at", { ascending: false })

      if (error) throw error
      return data
    },
  })
}

// Creating data with optimistic updates
const useCreatePost = () => {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: async (newPost) => {
      const { data, error } = await supabase
        .from("posts")
        .insert(newPost)
        .select()
        .single()

      if (error) throw error
      return data
    },
    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ["posts"] })
    },
  })
}
Key patterns:
  • React Query for caching and state management
  • Manual cache invalidation after mutations
  • Pull-to-refresh for manual data refresh
  • Optimistic updates for better UX

Using the Demo Screen

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.

Mock Database

In development without backend credentials, the app uses a mock database:
  • In-memory storage: Data persists during your session
  • Secure storage fallback: Critical data (like auth) persists across restarts
  • API compatibility: Same API as the real backend
See the Mock Services guide for details.

Next Steps