A production-ready mobile app template built with Expo Router, Supabase Auth, and TanStack Query.
- Authentication: Email/password auth with Supabase
- Push Notifications: Full FCM/APNs support via expo-notifications
- State Management: Zustand stores for auth, theme, and notifications
- Data Fetching: TanStack Query with optimized caching
- Theming: Light/dark/system mode support
- Error Handling: Error boundaries for screens and global shell
- TypeScript: Strict type checking throughout
- CI/CD: Codemagic configuration for iOS builds
npm installCopy the example environment file:
cp .env.example .envEdit .env with your Supabase credentials:
EXPO_PUBLIC_SUPABASE_URL=https://your-project-id.supabase.co
EXPO_PUBLIC_SUPABASE_PUB_KEY=your-anon-key-here
EXPO_PUBLIC_APP_ENV=developmentEdit app.json and replace placeholder values:
name: Your app nameslug: Your app slug (lowercase, hyphens)scheme: Your deep link scheme (lowercase, no special chars)ios.bundleIdentifier: Your iOS bundle ID (e.g.,com.yourcompany.yourapp)android.package: Your Android package nameextra.eas.projectId: Your EAS project ID (runeas initto create one)owner: Your Expo usernameupdates.url: Update with your EAS project ID
This template requires a Supabase backend with specific database tables and migrations. The Supabase configuration is maintained in a separate repository.
-
Clone the companion Supabase repository:
git clone <YOUR_SUPABASE_REPO_URL> supabase-backend cd supabase-backend
-
Start local Supabase:
supabase start
-
Run the migrations:
supabase db reset
-
Get your local credentials:
supabase status
Copy the
API URLandanon keyto your.envfile.
See the Supabase repository README for more details on the database schema and migrations.
npx expo startFor local development, use a .env file:
EXPO_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
EXPO_PUBLIC_SUPABASE_PUB_KEY=your-anon-key
EXPO_PUBLIC_APP_ENV=developmentFor EAS builds, configure environment variables in eas.json:
{
"build": {
"development": {
"env": {
"EXPO_PUBLIC_APP_ENV": "development",
"EXPO_PUBLIC_SUPABASE_URL": "your-dev-url",
"EXPO_PUBLIC_SUPABASE_PUB_KEY": "your-dev-key"
}
},
"production": {
"env": {
"EXPO_PUBLIC_APP_ENV": "production",
"EXPO_PUBLIC_SUPABASE_URL": "your-prod-url",
"EXPO_PUBLIC_SUPABASE_PUB_KEY": "your-prod-key"
}
}
}
}For sensitive values, use EAS secrets:
eas secret:create --name EXPO_PUBLIC_SUPABASE_URL --value "your-url"
eas secret:create --name EXPO_PUBLIC_SUPABASE_PUB_KEY --value "your-key"Then reference in eas.json:
{
"build": {
"production": {
"env": {
"EXPO_PUBLIC_SUPABASE_URL": "@EXPO_PUBLIC_SUPABASE_URL",
"EXPO_PUBLIC_SUPABASE_PUB_KEY": "@EXPO_PUBLIC_SUPABASE_PUB_KEY"
}
}
}
}- Create a Firebase project at https://console.firebase.google.com
- Add an Android app with your package name
- Download
google-services.jsonand place it in the project root - The file is already referenced in
app.json
- Configure push credentials in EAS:
eas credentials
- Select iOS, then push notifications
- EAS will guide you through creating/uploading push credentials
Push tokens are automatically stored in the users.fcm_tokens column when users sign in.
npx expo start # Start dev server
npx expo start --clear # Clear cache and startRequires EAS CLI installed globally (npm install -g eas-cli).
# Development builds (internal testing)
eas build --profile development --platform ios
eas build --profile development --platform android
# Production builds
eas build --profile production --platform ios
eas build --profile production --platform android
# Build and submit to stores
eas build --profile production --platform ios --auto-submit
eas build --profile production --platform android --auto-submit
# Over-the-air updates
eas update --branch production --message "Description of update"| Profile | Purpose |
|---|---|
development |
Dev client builds for internal distribution |
production |
Store builds with auto-increment versioning |
├── app/ # Expo Router file-based routing
│ ├── (auth)/ # Auth screens (email-auth, onboarding)
│ ├── (tabs)/ # Tab navigation (home, profile, edit-profile)
│ └── _layout.tsx # Root layout with providers
├── src/
│ ├── components/ # Reusable components
│ │ ├── ui/ # UI primitives (Button, Input, Text, Avatar)
│ │ ├── forms/ # Form components with react-hook-form
│ │ └── error/ # Error boundaries
│ ├── features/ # Feature modules
│ │ ├── auth/ # Authentication (stores)
│ │ └── profile/ # User profile (hooks)
│ ├── hooks/ # Global hooks (useColors, useIsDark)
│ ├── lib/ # Utilities (api, supabase, theme, queryClient, notifications)
│ ├── stores/ # Global Zustand stores (theme, notifications)
│ └── types/ # TypeScript types
├── assets/ # Static assets
├── app.json # Expo configuration
└── eas.json # EAS Build configuration
- Add types to
src/types/database.ts - Create feature folder:
src/features/yourfeature/ - Create hooks:
src/features/yourfeature/hooks/useYourFeature.ts - Export from feature index:
src/features/yourfeature/index.ts
Example hook structure (see src/features/items/hooks/useItems.ts):
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { supabase } from '@/src/lib/supabase'
export const yourFeatureKeys = {
all: ['yourfeature'] as const,
list: (userId: string) => [...yourFeatureKeys.all, 'list', userId] as const,
detail: (id: string) => [...yourFeatureKeys.all, 'detail', id] as const,
}
export function useYourFeatures(userId: string | undefined) {
return useQuery({
queryKey: yourFeatureKeys.list(userId ?? ''),
queryFn: async () => {
// Your query logic
},
enabled: !!userId,
})
}- Connect your repository to Codemagic
- Add App Store Connect API key to Codemagic integrations
- Update
codemagic.yamlwith your values:bundle_identifier: Your iOS bundle IDXCODE_WORKSPACE: Your workspace name (matches app name)XCODE_SCHEME: Your scheme name (matches app name)APP_ID: Your App Store Connect app ID- Environment variables for Supabase
Push to your repository to trigger builds, or manually trigger from Codemagic dashboard.
If you see errors in Expo Go, you likely need a development build:
eas build --profile development --platform ios
# or
eas build --profile development --platform androidExpo Go has limited native module support. Development builds include all native code.
Regenerate database types:
npx supabase gen types typescript --project-id your-project-id > src/types/database.ts- Check device is physical (simulators don't support push)
- Verify
google-services.jsonis present for Android - Verify iOS push credentials in EAS
- Check
fcm_tokenscolumn is being populated
MIT