Build a native mobile ordering app using React Native (Expo) and @craveup/storefront-sdk. The SDK is framework-agnostic and works anywhere fetch is available — including React Native.
Prerequisites
- Node.js 18+ and a package manager
- Expo CLI (
npx expo)
- A Crave API key from the Dashboard
- At least one live location in your merchant account
1. Create an Expo project
npx create-expo-app my-storefront --template blank-typescript
cd my-storefront
2. Install the SDK
npm install @craveup/storefront-sdk
3. Set environment variables
Create a .env file and install expo-constants to access them:
EXPO_PUBLIC_CRAVEUP_API_KEY=sk_live_your_api_key
EXPO_PUBLIC_LOCATION_ID=loc_your_location_id
EXPO_PUBLIC_LOCATION_SLUG=your-restaurant-slug
Expo exposes variables prefixed with EXPO_PUBLIC_ at build time via process.env.
4. Create the storefront client
import { createStorefrontClient } from '@craveup/storefront-sdk';
export const storefront = createStorefrontClient({
apiKey: process.env.EXPO_PUBLIC_CRAVEUP_API_KEY!,
});
React Native includes a global fetch implementation, so no polyfill is needed.
5. Display the merchant
import { useEffect, useState } from 'react';
import { View, Text, FlatList, StyleSheet } from 'react-native';
import { storefront } from '../lib/storefront';
import type { MerchantApiResponse } from '@craveup/storefront-sdk';
const SLUG = process.env.EXPO_PUBLIC_LOCATION_SLUG!;
export default function HomeScreen() {
const [merchant, setMerchant] = useState<MerchantApiResponse | null>(null);
useEffect(() => {
storefront.merchant.getBySlug(SLUG).then(setMerchant);
}, []);
if (!merchant) return <Text style={styles.loading}>Loading...</Text>;
return (
<View style={styles.container}>
<Text style={styles.title}>{merchant.name}</Text>
<Text style={styles.bio}>{merchant.bio}</Text>
<FlatList
data={merchant.locations}
keyExtractor={(loc) => loc.id}
renderItem={({ item }) => (
<View style={styles.card}>
<Text style={styles.cardTitle}>{item.restaurantDisplayName}</Text>
<Text style={styles.cardSub}>{item.addressString}</Text>
</View>
)}
/>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 24 },
loading: { padding: 24, textAlign: 'center' },
title: { fontSize: 28, fontWeight: 'bold' },
bio: { fontSize: 16, color: '#666', marginTop: 8 },
card: { padding: 16, borderWidth: 1, borderColor: '#ddd', borderRadius: 8, marginTop: 12 },
cardTitle: { fontSize: 18, fontWeight: '600' },
cardSub: { fontSize: 14, color: '#888', marginTop: 4 },
});
6. Create a cart hook
import { useState, useCallback } from 'react';
import { storefront } from '../lib/storefront';
import type { StorefrontCart } from '@craveup/storefront-sdk';
const LOCATION_ID = process.env.EXPO_PUBLIC_LOCATION_ID!;
export function useCart() {
const [cartId, setCartId] = useState<string | null>(null);
const [cart, setCart] = useState<StorefrontCart | null>(null);
const startSession = useCallback(async () => {
const session = await storefront.orderingSessions.start(LOCATION_ID, {
marketplaceId: LOCATION_ID,
});
if (session.cartId) setCartId(session.cartId);
return session.cartId;
}, []);
const addItem = useCallback(async (productId: string, quantity = 1) => {
const id = cartId ?? (await startSession());
if (!id) return;
const result = await storefront.cart.addItem(LOCATION_ID, id, {
productId,
quantity,
selections: [],
itemUnavailableAction: 'remove_item',
});
setCart(result.cart);
setCartId(result.cartId);
}, [cartId, startSession]);
const refreshCart = useCallback(async () => {
if (!cartId) return;
const updated = await storefront.cart.get(LOCATION_ID, cartId);
setCart(updated);
}, [cartId]);
return { cart, cartId, addItem, refreshCart, startSession };
}
7. Add-to-cart screen
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { useCart } from '../hooks/useCart';
export default function OrderScreen() {
const { cart, addItem } = useCart();
return (
<View style={styles.container}>
<Text style={styles.title}>Order</Text>
<TouchableOpacity style={styles.button} onPress={() => addItem('prod_margherita')}>
<Text style={styles.buttonText}>Add Margherita Pizza</Text>
</TouchableOpacity>
{cart && (
<View style={styles.cartSummary}>
<Text style={styles.cartText}>
{cart.totalQuantity} items — {cart.orderTotalWithServiceFeeFormatted}
</Text>
</View>
)}
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 24 },
title: { fontSize: 24, fontWeight: 'bold' },
button: { marginTop: 16, backgroundColor: '#0d9488', padding: 14, borderRadius: 8, alignItems: 'center' },
buttonText: { color: '#fff', fontSize: 16, fontWeight: '600' },
cartSummary: { marginTop: 24, padding: 16, borderWidth: 1, borderColor: '#ddd', borderRadius: 8 },
cartText: { fontSize: 16 },
});
8. Run on a device
Scan the QR code with the Expo Go app on your phone, or press i for the iOS simulator / a for the Android emulator.
API key security on mobile
Mobile apps ship API keys to the device. For production apps, proxy API requests through your own backend so keys never leave your server. The SDK’s baseUrl option lets you point at your proxy instead of api.craveup.com directly.
Next steps