Supabase Edge Function Proxy for Your Food API Key
Published June 16, 2026
Mobile apps ship JavaScript bundles that anyone can inspect. If your food API key lives in React Native source, someone will extract it and burn your quota. A Supabase Edge Function proxy keeps the key on Deno runtime secrets while your Expo app calls a URL you control.
This tutorial walks through a production-ready Supabase edge function proxy food API key setup using Calorie API.
Why Supabase Edge Functions for Nutrition APIs
| Benefit | Detail |
|---|---|
| Global edge deployment | Low latency for mobile users |
| Built-in secrets | CALORIE_API_KEY never in git |
| Deno runtime | fetch native, no extra HTTP client |
| Auth optional | Add Supabase JWT checks per user |
| Free tier | Enough for MVPs and staging |
Step 1: Store the Secret
supabase secrets set CALORIE_API_KEY=your_key_here
Never commit keys to .env files pushed to GitHub. Use Supabase dashboard or CLI for production.
Step 2: Create the Edge Function
supabase functions new food-search
Edit supabase/functions/food-search/index.ts:
import { serve } from 'https://deno.land/[email protected]/http/server.ts';
const CALORIE_API = 'https://api.calorieapi.com/api/v1';
const API_KEY = Deno.env.get('CALORIE_API_KEY');
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};
serve(async (req) => {
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders });
}
if (!API_KEY) {
return new Response(JSON.stringify({ error: 'Server misconfigured' }), {
status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
});
}
const url = new URL(req.url);
const q = url.searchParams.get('q')?.trim() ?? '';
const limit = url.searchParams.get('limit') ?? '10';
if (q.length < 2 || q.length > 100) {
return new Response(JSON.stringify({ error: 'Invalid query' }), {
status: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
});
}
const upstream = `NULL/search/foods?q=${encodeURIComponent(q)}&limit=${encodeURIComponent(limit)}`;
const upstreamRes = await fetch(upstream, {
headers: { 'X-API-Key': API_KEY },
});
const body = await upstreamRes.text();
return new Response(body, {
status: upstreamRes.status,
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
});
});
Input validation (q length) blocks abuse. Adjust CORS Allow-Origin to your app domain in production instead of *.
Step 3: Deploy
supabase functions deploy food-search
Your endpoint:
https://YOUR_PROJECT.supabase.co/functions/v1/food-search?q=apple&limit=5
Pass the Supabase anon key in the Authorization: Bearer header from the client if you enable JWT verification.
Step 4: Call From React Native
const SUPABASE_URL = process.env.EXPO_PUBLIC_SUPABASE_URL;
const SUPABASE_ANON = process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY;
export async function searchFoods(query: string) {
const url = `NULL/functions/v1/food-search?q=${encodeURIComponent(query)}&limit=10`;
const res = await fetch(url, {
headers: {
Authorization: `Bearer NULL`,
'Content-Type': 'application/json',
},
});
if (!res.ok) throw new Error(`Search failed: NULL`);
return res.json();
}
The Calorie API key stays on Supabase. The anon key is public by design; protect functions with RLS-style auth when you add user accounts.
Step 5: Add Barcode and Detail Functions
Duplicate the pattern for:
food-barcode→GET /search/barcode/{upc}food-detail→GET /foods/{id}
One function per route keeps cold starts predictable and logs readable.
Security Hardening
- Require Supabase Auth JWT for logged-in users only
- Rate limit per user ID in Postgres or Upstash Redis
- Validate all query params server-side
- Log upstream status codes, not full API keys
- Rotate Calorie API keys if abuse detected
Supabase vs Next.js Proxy
| Supabase Edge | Next.js API Route | |
|---|---|---|
| Best stack | Supabase backend + Expo | Next.js web + mobile |
| Runtime | Deno | Node |
| Secrets | supabase secrets | process.env on Vercel |
Both patterns work. Pick what matches your existing backend. See Next.js API route guide.
Related Guides
Frequently Asked Questions
How do I proxy a food API key with Supabase Edge Functions?
Store CALORIE_API_KEY in Supabase secrets, create a Deno function that validates input and forwards requests to Calorie API with the X-API-Key header, then call that function URL from your mobile app.
Is the Supabase anon key safe to embed in a mobile app?
The anon key is designed to be public. Protect Edge Functions with JWT verification and per-user rate limits so anonymous callers cannot abuse your food API quota.
Can Supabase Edge Functions call Calorie API?
Yes. Deno fetch supports HTTPS calls to api.calorieapi.com. Add the X-API-Key header from environment secrets on the server only.
Supabase or Next.js for a food API proxy?
Use Supabase if your app already uses Supabase Auth and database. Use Next.js API routes if your marketing site and backend are on Vercel. Both hide the food API key from mobile clients.
How do I prevent quota theft on a public Edge Function?
Require authenticated users, validate and length-limit query strings, add per-user rate limits, and monitor Calorie API usage in your dashboard.
