Off-premises dining orders now outpace in-restaurant dining, and consumers expect friction-free experiences like Uber Eats or DoorDash. You'll build that experience: a full-featured mobile app with user registration, dish browsing, cart management, and profiles—all from one codebase.
React Native delivers near-native performance on iOS and Android while sharing 90% of your code. Strapi v5 provides a modern headless CMS with auto-generated REST and GraphQL endpoints, TypeScript support, and streamlined content workflows.
You'll have a production-ready stack deployable to app stores and any cloud provider.
In Brief:
- React Native enables cross-platform development with 90%+ code sharing between iOS and Android
- Strapi v5 provides auto-generated APIs, JWT authentication, and role-based permissions for secure mobile backends
- Normalized state management and AsyncStorage create responsive UIs with offline functionality
- Production deployment requires proper signing, OTA updates, and monitoring for both mobile and backend infrastructure
Technology Stack Overview
React Native and Strapi v5 create a clean separation of concerns for mobile app development:
- React Native - Compiles to native views for near-native performance while sharing 90%+ code between iOS and Android
- React Navigation and AsyncStorage - Handle multi-screen flows and secure local data persistence with hot-reloading for faster development
- Strapi v5 - Provides auto-generated REST and GraphQL endpoints, JWT authentication, role-based permissions, and media handling
- Headless Architecture - Enables independent scaling, version control for content types, and future expansion to web or other platforms
The API-first approach keeps mobile code focused on UI while Strapi handles data management, authentication, and content workflows.
Part 1: Setting Up the Backend with Strapi v5
Build a robust content management foundation that handles user authentication, product catalogs, and order processing for your mobile app.
Setup and Install
Start by confirming your machine runs Node.js 18 or 20—earlier versions trigger package errors during installation. Create the project inside a dedicated backend
folder:
1npx create-strapi-app@latest backend --quickstart
The --quickstart
flag pulls in all dependencies, spins up a SQLite database, and launches the Admin Panel at http://localhost:1337/admin
in one step. You'll register an admin account on first load, then land in a clean dashboard ready for configuration.
For production environments, drop the flag and let the CLI prompt you for PostgreSQL or MySQL credentials instead. Your project structure is straightforward: /config
for environment settings, /src
for content-type definitions and custom code, and /public
for static assets. Start hot-reloading development anytime with npm run develop
.
Mobile developers sometimes experience port collisions with Strapi's default port 1337; if this occurs, change the port in config/server.js and restart to resolve the issue. Mismatched Node versions are the other common blocker—stick to the LTS releases recommended in the quick-start docs.
Design the Content Architecture
A food-delivery app lives or dies by clean data relationships, so map them before writing any code. Create four Collection Types with the Content-Type Builder:
Categories get simple name
and slug
fields. Products need title
, description
, price
(decimal), image
(Media), plus a many-to-one relation to Categories. Orders require total
, status
, timestamps, and a many-to-many relation to Products. Users rely on the built-in user model, extending it later with addresses or loyalty points as needed.
The unified document model means you avoid juggling separate JSON structures; relations are first-class citizens and returned in a flattened format that's trivial for a mobile client to parse.
Compared with a single "cart" JSON blob, this schema supports promotions, inventory checks, and future marketplace features without refactors. Whenever you add or tweak a field, the underlying API regenerates instantly, keeping your React Native layer in sync.
Configure and Secure the API
Moving beyond the basic setup, you'll need proper authentication and permissions. Open Settings → Users & Permissions to configure access control. Create roles for Customer, Restaurant Owner, and Admin, then toggle read/write access per endpoint.
Local authentication works out of the box—your app will hit POST http://localhost:1337/auth/local
with email and password, receiving a JWT to store securely on the device.
Enable CORS for your mobile bundle's scheme (http://localhost
during development, custom URI in production) inside config/middlewares.js
. Stick to HTTPS in production, set short JWT lifetimes, and review the authentication best practices guide for refresh-token strategies.
Test each generated route with Postman before wiring it into React Native; catching permission errors now saves hours of debugging later.
Part 2: React Native Project Foundation
Create a scalable mobile app structure with proper navigation, state management, and development tooling that supports both iOS and Android builds.
Set up the Development Environment
With your backend running, it's time to build the mobile client. Install the React Native CLI globally, then bootstrap your project with npx react-native init
. You'll need Xcode for iOS builds and Android Studio with the Android SDK for Android development.
Launch the iOS Simulator from Xcode ▶ Open Developer Tool ▶ Simulator, or start an Android Virtual Device from Android Studio's device manager.
PATH errors typically stem from missing Android SDK platform-tools
in your shell profile or unselected Xcode command-line tools in Preferences ▶ Locations. Match SDK versions between emulators and build.gradle
to prevent "unable to resolve symbol" build failures.
Choose bare React Native over Expo for this project. Direct native module access becomes essential for Stripe payments and encrypted storage. When custom native libraries join the mix later, you won't need to eject or maintain dual build pipelines.
Set up the Project Structure and Dependencies
Predictable file structure keeps growing codebases maintainable. Mirror the approach from the Digital Creations Co blueprint:
1/src
2 components/ # Reusable UI primitives
3 screens/ # Home, Cart, Profile, Restaurant...
4 services/ # API wrappers (auth, products, orders)
5 navigation/ # Stack, tab, and deep-link configs
6 state/ # Context or Redux logic
7 assets/ # Images, fonts, icons
8 utils/ # Helpers, formatters, constants
React Navigation handles multi-screen flows with @react-navigation/native
plus stack and tab packages. Axios manages HTTP requests to Strapi. For lightweight data persistence like JWTs or cart items, use @react-native-async-storage/async-storage
. Upgrade to Redux Toolkit only when global state becomes complex.
Both react-native-elements
and react-native-paper
provide production-ready components with theming support—critical for restaurant branding. Choose whichever aligns with your design language; both integrate cleanly with React Native's modern architecture.
Configure and Setup the Environment
Create a .env
file at your project root and load it with react-native-dotenv
. Store your Strapi API URL, Stripe publishable key, and feature flags there. Centralize network logic in /services/api.js
to switch between development, staging, and production backends with a single variable change.
Enable Hermes on Android and the new JSI architecture in Podfile
for iOS to reduce bundle size and improve startup time. Connect Flipper under react-native.config.js
for network inspection and React DevTools integration—useful when tracking JWT headers or debugging slow product list renders.
Add Jest setup plus Detox configuration to run unit tests and basic end-to-end flows on every commit. Keep secrets out of version control and always transmit over HTTPS, following React Native's security guidelines.
Part 3: Authentication and User Management
Food delivery apps require seamless authentication and reliable user data management. Building on your Strapi backend, you'll create a mobile interface that handles registration, login, session management, and profile updates.
Registration Implementation
Strapi's Users & Permissions plugin stores users and hashes passwords by default. Create a registration screen with controlled inputs for email, password, and confirmation. Validate inputs locally using yup
before making network requests:
1import axios from 'axios';
2
3export const register = async (email, password) => {
4 const { data } = await axios.post('http://localhost:1337/auth/local/register', {
5 username: email, // Strapi uses username but accepts email here
6 email,
7 password,
8 });
9 return data; // contains jwt and user
10};
Strapi returns a JWT and user object. Display loading states during requests and surface error.response.data.error.message
for user feedback. Use KeyboardAvoidingView
for registration screens and add accessibility labels for screen readers.
Login and Token Management
Login follows the same pattern—POST credentials to http://localhost:1337/auth/local
and store the returned token. Use react-native-keychain
for production apps instead of AsyncStorage to secure tokens in iOS Keychain and Android Keystore. Add Authorization: Bearer ${jwt}
headers to subsequent requests.
For social authentication, enable providers in Strapi settings and follow the same JWT flow. The mobile implementation remains unchanged regardless of authentication method.
Session Persistence
On app launch, retrieve the stored JWT and validate it against /users/me
. Navigate directly to the home screen if valid. Set reasonable token lifetimes in Strapi and handle 401 responses by prompting re-authentication. Listen for AppState
changes to check token validity when the app returns to foreground.
Profile Management
Expose profile functionality through a reusable hook:
1export const updateProfile = async (jwt, payload) =>
2 axios.put('http://localhost:1337/api/users/me', payload, {
3 headers: { Authorization: `Bearer ${jwt}` },
4 });
For avatar uploads, POST to Strapi's /upload
endpoint and attach the returned file ID to the user record. Compress images before upload to optimize mobile performance. Queue failed updates locally and retry when NetInfo
indicates connectivity.
Store user preferences—notification settings, default addresses, dietary filters—as JSON in Strapi's user collection. The same profile endpoint handles both personal details and preference updates, keeping the API simple. For logout, delete the stored token from secure storage and clear in-memory state. This immediately revokes access without requiring server-side token invalidation.
Part 4: Product Catalog and Display
Transform your Strapi product data into a fast, searchable mobile interface with optimized image loading and responsive navigation.
Product Data Management
With authentication in place, you can now focus on the core catalog functionality. Your Product
collection type in Strapi already includes name, description, price, category relation, and media fields. The headless CMS exposes these through REST and GraphQL endpoints automatically.
On mobile, treat product data as first-class application state. Normalize every response—store products in an object keyed by id
and keep category IDs as references. This structure keeps updates predictable and helps you avoid expensive deep clones.
Images represent the largest payload in food-delivery apps. Strapi can be configured to generate multiple image sizes and request thumbnails using plugins or external services. When users open a detail screen, lazily load the full-resolution URL. If you serve media from a CDN, switch the base URL through environment variables.
For network robustness, cache product lists locally with React Query or Redux Persist. On launch, hydrate the UI from cache and re-validate against Strapi—users see a populated menu even on slow connections. If the API call fails, render cached data and surface a non-blocking toast.
Expose boolean flags like available
or featured
in the Product schema. Marketing teams can toggle out-of-stock items or flash deals without code changes.
Product Listing and Search
Render the catalog with FlatList
. React Native only keeps visible rows in memory, giving you near-constant scroll performance even with thousands of dishes. Combine FlatList
with react-native-fast-image
to decode images off the main thread and avoid dropped frames.
Search needs to feel instantaneous. Keep the query string in local state and debounce the API call by 300 ms. Strapi supports filter parameters (/api/products?filters[name][$containsi]=sushi
), so you avoid sending full lists back to the device. For category chips and sort toggles, request only the subset you need—over-fetching kills battery life and data plans.
Pagination becomes mandatory once the database grows. Store the page
cursor returned by Strapi. When users near the end of the list, increment the cursor and concatenate results. Use onEndReachedThreshold={0.5}
so fetching starts before users hit the bottom, and show a lightweight skeleton loader to signal progress.
Pull-to-refresh re-queries page 1 and swaps the local cache. Because every product lives in a normalized store, merging pages or refreshing becomes a single reducer operation, not a full re-render. Wrap each item in a touchable card with height-consistent images, rounded corners, and a subtle shadow. Users focus on the meal, not visual glitches.
Product Detail Views
Tap a card and navigate to a dedicated screen that pulls the full product record by id
. Display an image carousel first—prefetch the next image while the current one is in view so swipes feel native-fast.
Below the fold, render price, customizable options, and nutritional info. A "You might also like" section queries Strapi for products in the same category, giving you cross-sell without extra backend work.
Let users rate dishes by posting ratings to a Review
collection and updating the average score in state. Add a share button that invokes the platform share sheet with the product deep link—free social marketing and minimal code.
Part 5: Shopping Cart and Order Management
Build a complete transaction flow from cart management through payment processing, with persistent state and real-time order tracking.
Cart Functionality
Represent the cart in a centralized state slice using Redux Toolkit. Effective state management patterns normalize items, store only IDs, and keep the UI responsive as the cart grows. Key every item by productId
, so quantity changes become O(1) lookups instead of full-array scans—crucial for mid-range devices.
When you add, increase, or remove items, compute the derived values—subtotal, tax, and delivery fee—in the same reducer. This eliminates extra renders and keeps numbers consistent across screens.
For persistence, hydrate the cart from AsyncStorage
on app launch and write back after every mutation. Because AsyncStorage
is asynchronous, queue writes behind a debounce timer so you don't block the JS thread during rapid quantity taps.
For cross-device sync, replace local storage with Strapi and generate a lightweight cart
collection type, but guard the endpoint with the Authenticated role only.
Mobile catalogs change fast—items sell out or prices adjust. Each time the cart screen mounts, fire a HEAD /products/{id}
request to Strapi and compare the returned UpdatedAt
header with the timestamp stored next to the cart item. When they differ, surface a non-blocking toast telling the user the item has changed, then refresh its price.
For badge indicators, expose a memoized selector that returns cart.totalQuantity
instead of polling the global store on every render. React Navigation's tab bar re-renders only when this value changes, keeping the frame rate high.
Order Processing
Submitting an order requires a two-step handshake. First, call your secure payment SDK—@stripe/stripe-react-native
handles the financial transaction. After receiving a payment intent, send a POST request to /orders
in Strapi with:
1{
2 "items": [{ "productId": 42, "quantity": 2 }],
3 "amount": 2380,
4 "paymentIntentId": "pi_123",
5 "status": "pending"
6}
Strapi stores the order and returns the canonical record, including its current status
. From here, display a confirmation screen that listens for server-side updates. Poll /orders/{id}
every 10 seconds and transition the UI through confirmed
, preparing
, and delivered
. Because Strapi exposes timestamps on every record, calculating ETA is as easy as subtracting preparingAt
from createdAt
.
For historical orders, create a dedicated screen that fetches /orders?filters[user][id][$eq]=<currentUser>
and supports pull-to-refresh. Each card includes a "Re-order" button that re-hydrates the cart slice using the original line items—a five-line reducer because the data is already normalized.
Address management rides on the same content-type builder you used earlier. Give Address
a many-to-one relation with User
, expose the collection under the Authenticated role, and your checkout form can auto-fill the most recent address. When you need to debug, open the Strapi Admin Panel and you'll see orders, addresses, and user roles in one place.
Part 6: Advanced Features and Polish
With your core functionality complete, focus shifts to creating a polished user experience. A refined food-delivery app depends on smooth navigation and consistent performance across devices.
Navigation Structure
- React Navigation handles the three patterns you need:
- Stack navigation for linear flows
- Bottom tabs for primary sections
- Optional drawer for secondary menus
- Wire these navigators together so users can jump from a "Home" tab into a nested Restaurant stack, then slide out a drawer for settings without losing state
- Keep touch targets at least 44 dp
- Avoid gesture conflicts by reserving horizontal swipes for drawers only
Animations and User Experience
- When pushing a new screen:
- Fade in images and stagger text using the native-driver powered
Animated
API - These small touches make the interface feel alive without blocking the JS thread
- Fade in images and stagger text using the native-driver powered
- Handle deep links by:
- Registering your custom scheme in the linking config
- Mapping notification payloads to routes (e.g., "Your order is on the way" push opens the tracking screen)
- Error handling:
- Wrap each async boundary (menu fetch, order status poll) in a suspense-style loader
- Implement a global error boundary so the app degrades gracefully instead of crashing
Performance Optimization
- Data flow improvements:
- Normalize product, cart, and order objects before they hit the UI
- Keep flat structures to prevent expensive deep clones during updates
- List rendering:
- Use
FlatList
for large lists - Supply
getItemLayout
plus a stablekeyExtractor
- Avoid layout passes and jank when scrolling through hundreds of dishes
- Use
- Component optimization:
- Memoize row components with
React.memo
- Cache calculated selectors with
useMemo
- These practices cut unnecessary re-renders by up to 30%
- Memoize row components with
Image and Data Management
- Image loading strategy:
- Serve thumbnail URLs from your backend
- Swap in high-resolution versions via
react-native-fast-image
- Keep initial payloads light and let the cache handle repeat visits
- Offline support:
- Persist hot data (categories, recent orders, auth tokens) using AsyncStorage
- Hydrate on app launch
- Cache frequently requested endpoints to shave seconds off startup on older devices
Final Optimizations
- Enable Hermes in your
android/app/build.gradle
to:- Trim bundle size
- Speed up JIT compilation
- Guard every network call with exponential-backoff retries to prevent flaky connections from causing dropped orders
Troubleshooting Development Challenges
Mobile app development introduces unique challenges around performance, integration, and device compatibility—here's how to identify and resolve the most frequent issues.
React Native Development Issues
Cryptic red screens and sluggish navigation kill productivity fast. The most common culprits are state-management bloat, bundler configuration issues, and version drift.
When your store grows unchecked, deeply nested objects trigger expensive re-renders. Flatten and normalize product data and avoid nested traps through proper selector implementation.
Metro bundler crashes ("Unable to resolve module…") usually trace back to mismatched package versions. Wipe node_modules
, clear the cache with npx react-native start --reset-cache
, then rebuild. Native module linking errors often resolve after running platform-specific commands—pod install
for iOS or gradle sync for Android.
For runtime debugging, Chrome DevTools paired with Flipper's network and layout plugins provide comprehensive insights. Keep React Native Debugger open for Redux traces. Schedule regular upgrade windows since jumping multiple React Native versions breaks community libraries.
Strapi v5 Integration Challenges
Rock-solid mobile UI means nothing when the backend fails. CORS issues appear first—add your device's local IP to the allowedOrigins
array in ./config/middlewares.js
before attempting any fetch requests.
Authentication problems surface when JWT tokens aren't persisted securely or expire silently. Strapi's guides on authentication and authorization explain how Strapi signs and validates tokens.
Queries returning empty arrays after schema changes usually point to misconfigured relationships in the Content-Type Builder. Strapi v5's flattened responses expose one-to-many configuration errors quickly.
Media uploads vanishing in production typically indicate missing environment variables for the upload provider. Review managing multiple environments for staging-safe configurations.
Before releasing, configure webhooks in Settings → Webhooks for real-time order status updates.
Mobile-Specific Considerations
Mobile networks drop, batteries drain, and screens rotate—plan for these realities. Cache critical data like cart contents in encrypted storage, then reconcile when connectivity returns. Store tokens in the Keychain or Keystore following React Native security best practices.
Request location, camera, or file access only when needed. Broad upfront permission requests hurt conversion and violate store policies. Handle orientation changes through percentage-based styles and tablet testing.
Minimize energy usage by debouncing API polling and favoring push-based updates.
Deployment and Distribution
Before you press "publish," you need two reliable release pipelines—one for the mobile binary and one for Strapi.
- Prepare the React Native project for production
- Generate app icons, splash screens, and release builds
- Sign them with the iOS (.p12) and Android (.keystore) certificates Apple and Google require
- Upload a test build through TestFlight or Google Play Internal Testing
- Run through every core flow—authentication, cart, checkout—using the same production API URL you plan to expose publicly
- Configure the backend for production
- Switch the default SQLite database to PostgreSQL or MySQL before launch
- The Strapi CLI prompts you for credentials when you omit the
--quickstart
flag - Use separate databases for staging and production so test data never pollutes real orders
- Host Strapi appropriately
- Choose a platform you're comfortable with—AWS, DigitalOcean, or the managed Strapi Cloud
- Always front it with HTTPS and a load balancer so you can add nodes when traffic spikes
- Off-load images to a CDN to keep API responses lean and lower Time to First Byte
- Automate your deployment process
- Set up a GitHub Actions pipeline to lint, test, and build the React Native release, then trigger Fastlane for store uploads
- Create a parallel job to Docker-build Strapi, run migrations, and deploy to your cloud host
- Keep both pipelines idempotent so a green commit is always shippable
- Plan for post-release updates
- Ship bug fixes through over-the-air updates with CodePush or Expo Updates
- These OTA patches bypass app-store review while respecting platform policies because only JavaScript bundles change—native code remains untouched
- Implement monitoring solutions
- Use tools like Sentry to capture JavaScript exceptions in the mobile app
- Set up cloud dashboards to track backend CPU, memory, and database health
- With these guardrails in place, you can iterate quickly without sacrificing stability or security
From Concept to Mobile Marketplace
You now have a food-delivery app that runs on iOS and Android, powered by a Strapi v5 backend. You set up content models, secured endpoints with JWT, wired React Navigation, and optimized state management.
This pairing works because Strapi's flattened API responses, role-based permissions, and admin UI give you reliable data, while React Native lets you reuse code and ship quickly.
The patterns you've learned—normalized state, secure authentication, and cached data—apply to any mobile app that needs structured content and user management.