Overview

This guide will help you build a basic restaurant storefront using the Crave API. You’ll have a working menu and cart system in under 30 minutes.

Prerequisites

  • Node.js 18+ and npm/yarn
  • Next.js project (or create one with npx create-next-app@latest)
  • Paid Crave subscription (required for API access)
  • API key (see below for how to obtain)

Step 1: Get Your API Key

Option 1: Developer Dashboard

Visit dashboard.craveup.com/developers to generate your API key:
  1. Sign up or log in to your Crave developer account
  2. Navigate to the API Keys section
  3. Click “Create New API Key”
  4. Name your key (e.g., “My Storefront API Key”)
  5. Copy your API key - Store it securely (you won’t see it again)
  6. Note your Location ID - This will be provided in your dashboard

Option 2: Request from Merchant

If you’re building for a specific merchant, contact them directly to obtain your API key and location ID.

Option 3: Generate via API (Enterprise Only)

Admin APIs unlock powerful automation capabilities for large-scale operations. If you’re building multi-tenant platforms, managing hundreds of locations, creating white-label solutions, or need programmatic control over merchant settings, admin API access can streamline your operations significantly. Key Benefits of Admin API Access:
  • Automated onboarding of new merchants and locations
  • Bulk management of menus, pricing, and inventory across multiple locations
  • Dynamic API key generation for client applications
  • Real-time sync with your existing restaurant management systems
  • Custom analytics and reporting across your entire merchant network
Admin APIs are available for Enterprise tier customers only. To generate API keys programmatically:
  1. Contact support at hello@craveup.com for beta access request
  2. Provide your use case and Enterprise tier details
  3. Receive admin API documentation and authentication tokens
  4. Generate keys programmatically:
curl -X POST https://api.cravejs.com/api/internal/accounts/api-keys \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Storefront API Key"
  }'
Note: This option requires Enterprise tier subscription and beta access approval.

Step 2: Environment Setup

Create a .env.local file in your project root:
# API Configuration (see Introduction for base URL details)
CRAVE_API_KEY=your_api_key_here
CRAVE_API_BASE_URL=https://api.cravejs.com

# Location Configuration  
NEXT_PUBLIC_LOCATION_ID=your_location_id_here

# Stripe Configuration (for payments)
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...

Test Environment

For development and testing, you can use these test values:
# Test Environment - connects to production API with test location
CRAVE_API_BASE_URL=https://api.cravejs.com
CRAVE_API_KEY=your_api_key_here
NEXT_PUBLIC_LOCATION_ID=your_location_id_here
Note: The test environment uses the production API with a test location and API key provided for development purposes.

Step 3: Install Dependencies

npm install @stripe/stripe-js @stripe/react-stripe-js

Step 4: Create API Client

Create lib/api-client.js:
const API_BASE_URL = process.env.CRAVE_API_BASE_URL;
const API_KEY = process.env.CRAVE_API_KEY;

export async function craveApi(endpoint, options = {}) {
  const url = `${API_BASE_URL}/api/v1${endpoint}`;
  
  const response = await fetch(url, {
    ...options,
    headers: {
      'X-API-Key': API_KEY,
      'Content-Type': 'application/json',
      ...options.headers
    }
  });
  
  if (!response.ok) {
    throw new Error(`API request failed: ${response.status}`);
  }
  
  return response.json();
}

Step 5: Create Menu Component

Create components/Menu.js:
import { useState, useEffect } from 'react';
import { craveApi } from '../lib/api-client';

export default function Menu({ locationId, onAddToCart }) {
  const [menu, setMenu] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    async function fetchMenu() {
      try {
        const data = await craveApi(`/api/v1/locations/${locationId}/menus`);
        setMenu(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }
    
    fetchMenu();
  }, [locationId]);
  
  if (loading) return <div>Loading menu...</div>;
  if (error) return <div>Error: {error}</div>;
  if (!menu) return <div>No menu available</div>;
  
  return (
    <div className="menu">
      <h2>Menu</h2>
      {menu.categories?.map(category => (
        <div key={category.id} className="category">
          <h3>{category.name}</h3>
          <div className="products">
            {category.products?.map(product => (
              <div key={product.id} className="product">
                <h4>{product.name}</h4>
                <p>{product.description}</p>
                <p className="price">${(product.price / 100).toFixed(2)}</p>
                <button onClick={() => onAddToCart(product)}>
                  Add to Cart
                </button>
              </div>
            ))}
          </div>
        </div>
      ))}
    </div>
  );
}

Step 6: Create Cart Component

Create components/Cart.js:
import { useState } from 'react';

export default function Cart({ items, onUpdateQuantity, onRemoveItem }) {
  const total = items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
  
  return (
    <div className="cart">
      <h2>Cart</h2>
      {items.length === 0 ? (
        <p>Your cart is empty</p>
      ) : (
        <>
          {items.map(item => (
            <div key={item.id} className="cart-item">
              <h4>{item.name}</h4>
              <div className="quantity-controls">
                <button onClick={() => onUpdateQuantity(item.id, item.quantity - 1)}>
                  -
                </button>
                <span>{item.quantity}</span>
                <button onClick={() => onUpdateQuantity(item.id, item.quantity + 1)}>
                  +
                </button>
              </div>
              <p className="price">${(item.price / 100).toFixed(2)}</p>
              <button onClick={() => onRemoveItem(item.id)}>Remove</button>
            </div>
          ))}
          <div className="cart-total">
            <strong>Total: ${(total / 100).toFixed(2)}</strong>
          </div>
        </>
      )}
    </div>
  );
}

Step 7: Create Main Page

Update pages/index.js:
import { useState } from 'react';
import Menu from '../components/Menu';
import Cart from '../components/Cart';

export default function Home() {
  const [cartItems, setCartItems] = useState([]);
  const locationId = process.env.NEXT_PUBLIC_LOCATION_ID;
  
  const addToCart = (product) => {
    setCartItems(prevItems => {
      const existingItem = prevItems.find(item => item.id === product.id);
      
      if (existingItem) {
        return prevItems.map(item =>
          item.id === product.id
            ? { ...item, quantity: item.quantity + 1 }
            : item
        );
      } else {
        return [...prevItems, { ...product, quantity: 1 }];
      }
    });
  };
  
  const updateQuantity = (productId, newQuantity) => {
    if (newQuantity <= 0) {
      removeFromCart(productId);
      return;
    }
    
    setCartItems(prevItems =>
      prevItems.map(item =>
        item.id === productId
          ? { ...item, quantity: newQuantity }
          : item
      )
    );
  };
  
  const removeFromCart = (productId) => {
    setCartItems(prevItems => prevItems.filter(item => item.id !== productId));
  };
  
  return (
    <div style={{ display: 'flex', gap: '2rem', padding: '2rem' }}>
      <div style={{ flex: 2 }}>
        <Menu locationId={locationId} onAddToCart={addToCart} />
      </div>
      <div style={{ flex: 1 }}>
        <Cart 
          items={cartItems}
          onUpdateQuantity={updateQuantity}
          onRemoveItem={removeFromCart}
        />
      </div>
    </div>
  );
}

Step 8: Add Basic Styling

Create styles/globals.css:
.menu {
  max-width: 800px;
}

.category {
  margin-bottom: 2rem;
}

.products {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 1rem;
}

.product {
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 1rem;
}

.price {
  font-weight: bold;
  color: #007bff;
}

.cart {
  background: #f8f9fa;
  padding: 1rem;
  border-radius: 8px;
  position: sticky;
  top: 2rem;
}

.cart-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.5rem 0;
  border-bottom: 1px solid #eee;
}

.quantity-controls {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.cart-total {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 2px solid #007bff;
  text-align: right;
}

Step 9: Test Your Storefront

  1. Start your development server:
npm run dev
  1. Open http://localhost:3000
  2. Verify that:
    • Menu loads correctly
    • You can add items to cart
    • Cart updates properly
    • No console errors

Next Steps

Now that you have a basic storefront:
  1. Add Payments: Follow the Stripe Integration Guide
  2. Improve UI: Add better styling and components
  3. Add Features: Customer info, order scheduling, etc.
  4. Error Handling: Add proper error boundaries
  5. Performance: Add loading states and caching

Common Issues

  • Menu not loading: Check your API key and location ID
  • CORS errors: Make sure you’re using server-side API calls
  • Rate limiting: Implement proper caching to avoid hitting limits
Your basic storefront is now ready! This foundation can be extended with payments, customer management, and more advanced features.