Enterprise grade patterns
Hereβs how to implement enterprise-grade patterns in an Expo + Bun app using TanStack Query + Zustand + NativeWind.
This is the stuff that separates mid-level apps from production SaaS/mobile systems.
π§ 1. Offline-First Architecture (Sync Engine)
Section titled βπ§ 1. Offline-First Architecture (Sync Engine)βπ― Goal
Section titled βπ― GoalβApp works even:
- No internet
- Flaky network
- Background sync
π¦ Core Idea
Section titled βπ¦ Core Ideaβ- Cache server data (React Query)
- Store pending mutations locally
- Sync when online
π src/services/offline/queue.ts
Section titled βπ src/services/offline/queue.tsβtype QueueItem = { id: string; url: string; method: 'POST' | 'PUT' | 'DELETE'; body: any;};
let queue: QueueItem[] = [];
export const addToQueue = (item: QueueItem) => { queue.push(item);};
export const processQueue = async (apiClient: any) => { for (const item of queue) { try { await apiClient({ url: item.url, method: item.method, data: item.body, }); } catch (e) { return; // stop if still offline } } queue = [];};π src/hooks/useOnlineSync.ts
Section titled βπ src/hooks/useOnlineSync.tsβimport NetInfo from '@react-native-community/netinfo';import { useEffect } from 'react';import { processQueue } from '@/services/offline/queue';import { apiClient } from '@/services/api/client';
export const useOnlineSync = () => { useEffect(() => { const unsubscribe = NetInfo.addEventListener((state) => { if (state.isConnected) { processQueue(apiClient); } });
return unsubscribe; }, []);};π Call this once in root layout.
π‘ Mutation Strategy (CRITICAL)
Section titled βπ‘ Mutation Strategy (CRITICAL)βimport { addToQueue } from '@/services/offline/queue';
const mutation = useMutation({ mutationFn: async (data) => { try { return await apiClient.post('/todos', data); } catch { addToQueue({ id: Date.now().toString(), url: '/todos', method: 'POST', body: data, }); } },});β‘ 2. Advanced Caching Strategy
Section titled ββ‘ 2. Advanced Caching Strategyβπ src/lib/react-query.ts
Section titled βπ src/lib/react-query.tsβimport { QueryClient } from '@tanstack/react-query';
export const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60 * 5, cacheTime: 1000 * 60 * 60, retry: 2, refetchOnReconnect: true, refetchOnWindowFocus: false, }, },});π₯ Persist Cache (Offline Cache)
Section titled βπ₯ Persist Cache (Offline Cache)βUse storage (MMKV recommended):
import { persistQueryClient } from '@tanstack/react-query-persist-client';import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister';import AsyncStorage from '@react-native-async-storage/async-storage';
persistQueryClient({ queryClient, persister: createAsyncStoragePersister({ storage: AsyncStorage, }),});π‘ Cache Invalidation Pattern
Section titled βπ‘ Cache Invalidation PatternβqueryClient.invalidateQueries({ queryKey: ['user'] });π Always invalidate after mutations.
π‘οΈ 3. Error Boundaries (Crash Safety)
Section titled βπ‘οΈ 3. Error Boundaries (Crash Safety)βπ¦ Install
Section titled βπ¦ Installβbun add react-error-boundaryπ src/components/ErrorBoundary.tsx
Section titled βπ src/components/ErrorBoundary.tsxβimport { ErrorBoundary } from 'react-error-boundary';import { View, Text, Button } from 'react-native';
function Fallback({ error, resetErrorBoundary }: any) { return ( <View className="flex-1 items-center justify-center"> <Text>Something went wrong</Text> <Button title="Retry" onPress={resetErrorBoundary} /> </View> );}
export const AppErrorBoundary = ({ children }: any) => ( <ErrorBoundary FallbackComponent={Fallback}> {children} </ErrorBoundary>);β Wrap Root
Section titled ββ Wrap Rootβ<AppErrorBoundary> <QueryClientProvider client={queryClient}> <Stack /> </QueryClientProvider></AppErrorBoundary>π 4. Logging & Monitoring (Enterprise MUST)
Section titled βπ 4. Logging & Monitoring (Enterprise MUST)βπ― What to log
Section titled βπ― What to logβ- API failures
- App crashes
- User actions (important flows)
- Performance
π src/services/logger.ts
Section titled βπ src/services/logger.tsβexport const logger = { log: (...args: any[]) => { console.log('[LOG]', ...args); }, error: (error: any) => { console.error('[ERROR]', error); // send to Sentry/Datadog later },};π Axios Interceptor Logging
Section titled βπ Axios Interceptor LoggingβapiClient.interceptors.response.use( (res) => res, (error) => { logger.error({ url: error.config?.url, message: error.message, }); return Promise.reject(error); });π₯ Production Tools (Recommended)
Section titled βπ₯ Production Tools (Recommended)β| Tool | Purpose |
|---|---|
| Sentry | Crash reporting |
| Datadog | Logs + APM |
| Firebase Analytics | User behavior |
π 5. Retry + Resilience Pattern
Section titled βπ 5. Retry + Resilience PatternβReact Query already supports retry:
useQuery({ queryKey: ['data'], queryFn: fetchData, retry: 3, retryDelay: (attempt) => attempt * 1000,});π§± 6. Clean Architecture Layers
Section titled βπ§± 6. Clean Architecture LayersβUI (app/)βHooks (features/*/hooks.ts)βAPI (features/*/api.ts)βService (api client)π 7. Bonus: Secure Storage Pattern
Section titled βπ 7. Bonus: Secure Storage Patternβimport * as SecureStore from 'expo-secure-store';
export const storage = { set: (key: string, value: string) => SecureStore.setItemAsync(key, value),
get: (key: string) => SecureStore.getItemAsync(key),};π Real Production Flow
Section titled βπ Real Production Flowβ- User opens app β cached data loads instantly
- API refetch runs in background
- Offline β mutations queued
- Online β auto sync
- Errors β caught by boundary + logged
- Logs β sent to monitoring
β οΈ Common Mistakes (Avoid These)
Section titled ββ οΈ Common Mistakes (Avoid These)ββ Not persisting cache β bad UX offline β No retry logic β fragile app β Logging only console β useless in prod β No queue β data loss offline β Overusing Zustand for server data
π‘ Senior Insight
Section titled βπ‘ Senior Insightβ- React Query = server state
- Zustand = client/UI state
- Offline queue = reliability layer
- Error boundary = safety net
- Logging = visibility
If you want next level (real enterprise stuff), I can show you:
- π₯ Conflict resolution (offline edits vs server truth)
- β‘ Background sync with Expo Task Manager
- π§ Event-driven architecture inside React Native
- π Full CI/CD + monitoring pipeline (Sentry + EAS + GitHub Actions)
Just tell me π