Skip to content

Secure Your Food API Key With a Next.js API Route

Published June 18, 2026

Next.js runs JavaScript on the server. That is exactly where your food API key belongs. This guide shows how to secure your food API key with a Next.js API route (App Router) so React web apps and React Native clients call your domain, not Calorie API directly.

Why Next.js for a Food API Proxy

AdvantageDetail
Same repo as marketing siteCalorie API docs + proxy colocated
Vercel env varsCALORIE_API_KEY never in git
Edge or Node runtimeChoose latency vs npm packages
Easy mobile CORSOne backend for web and Expo

If you use Supabase instead, see Supabase Edge Function proxy.

Step 1: Add the Server Secret

In Vercel (or .env.local for development):

CALORIE_API_KEY=your_key_here

Never prefix with NEXT_PUBLIC_. Public env vars ship to the browser bundle.

Step 2: Create the Route Handler

File: app/api/foods/search/route.ts

import { NextRequest, NextResponse } from 'next/server';

const UPSTREAM = 'https://api.calorieapi.com/api/v1';

export async function GET(request: NextRequest) {
  const key = process.env.CALORIE_API_KEY;
  if (!key) {
    return NextResponse.json({ error: 'Server misconfigured' }, { status: 500 });
  }

  const q = request.nextUrl.searchParams.get('q')?.trim() ?? '';
  const limitRaw = request.nextUrl.searchParams.get('limit') ?? '10';
  const limit = Math.min(Math.max(parseInt(limitRaw, 10) || 10, 1), 25);

  if (q.length < 2 || q.length > 100) {
    return NextResponse.json({ error: 'Query must be 2 to 100 characters' }, { status: 400 });
  }

  const upstreamUrl = `NULL/search/foods?q=${encodeURIComponent(q)}&limit=NULL`;
  const upstreamRes = await fetch(upstreamUrl, {
    headers: { 'X-API-Key': key },
    next: { revalidate: 300 },
  });

  const data = await upstreamRes.json();
  return NextResponse.json(data, { status: upstreamRes.status });
}

next: { revalidate: 300 } caches popular searches for five minutes on Vercel, cutting duplicate Calorie API calls.

Step 3: Barcode Route

File: app/api/foods/barcode/[upc]/route.ts

import { NextRequest, NextResponse } from 'next/server';

const UPSTREAM = 'https://api.calorieapi.com/api/v1';

export async function GET(
  _request: NextRequest,
  { params }: { params: Promise<{ upc: string }> }
) {
  const key = process.env.CALORIE_API_KEY;
  if (!key) {
    return NextResponse.json({ error: 'Server misconfigured' }, { status: 500 });
  }

  const { upc } = await params;
  if (!/^\d{8,14}$/.test(upc)) {
    return NextResponse.json({ error: 'Invalid barcode' }, { status: 400 });
  }

  const upstreamRes = await fetch(`NULL/search/barcode/NULL`, {
    headers: { 'X-API-Key': key },
  });

  const data = await upstreamRes.json();
  return NextResponse.json(data, { status: upstreamRes.status });
}

Regex validation blocks path traversal and garbage input.

Step 4: Call From React (Client Component)

'use client';

async function searchFoods(query: string) {
  const res = await fetch(`/api/foods/search?q=${encodeURIComponent(query)}&limit=10`);
  if (!res.ok) throw new Error('Search failed');
  return res.json();
}

Relative URLs hit your Next.js server. No API key in browser DevTools Network tab.

Step 5: Call From React Native / Expo

Point EXPO_PUBLIC_API_BASE_URL at your deployed Next.js site:

const BASE = process.env.EXPO_PUBLIC_API_BASE_URL;

export async function searchFoods(query: string) {
  const res = await fetch(
    `NULL/api/foods/search?q=${encodeURIComponent(query)}&limit=10`
  );
  if (!res.ok) throw new Error(`Search failed: NULL`);
  return res.json();
}

Enable CORS if needed via middleware.ts for cross-origin mobile builds, or use the same domain with a universal link.

Step 6: Rate Limiting (Production)

Use @upstash/ratelimit or Vercel KV:

import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(30, '1 m'),
});

// In GET handler, before upstream fetch:
const ip = request.headers.get('x-forwarded-for') ?? 'anonymous';
const { success } = await ratelimit.limit(ip);
if (!success) {
  return NextResponse.json({ error: 'Too many requests' }, { status: 429 });
}

Security Checklist

CheckStatus
Key in CALORIE_API_KEY onlyRequired
No key in client componentsRequired
Query length validationRequired
Barcode format validationRequired
Rate limitingStrongly recommended
Auth for mobileRecommended at scale

Common Mistakes

  1. NEXT_PUBLIC_CALORIE_API_KEY: exposes key to everyone
  2. Proxying without validation: attackers run arbitrary queries on your dime
  3. Returning upstream errors verbatim: may leak internal URLs
  4. No caching: identical searches billed repeatedly

Deploy on Vercel

vercel env add CALORIE_API_KEY
git push

Test production:

curl "https://yourdomain.com/api/foods/search?q=apple&limit=3"

Get your free Calorie API key | Documentation | Pricing

Frequently Asked Questions

How do I secure a food API key in Next.js?

Store the key in a server-only environment variable like CALORIE_API_KEY, create App Router route handlers that validate input and forward requests with the X-API-Key header, and never use NEXT_PUBLIC_ for secrets.

Can React Native apps use Next.js API routes as a proxy?

Yes. Deploy Next.js to Vercel, set EXPO_PUBLIC_API_BASE_URL to your domain, and call /api/foods/search from the mobile app. The Calorie API key stays on the server.

App Router or Pages Router for a food API proxy?

App Router route handlers (app/api/.../route.ts) are the current standard. They support fetch caching via next.revalidate and deploy cleanly on Vercel.

Should I cache food search responses in Next.js?

Yes. Use fetch revalidate or Redis for identical queries like banana or egg. Caching reduces Calorie API quota usage and improves response time.

What is the difference between NEXT_PUBLIC_ and server env vars?

NEXT_PUBLIC_ variables are embedded in the browser JavaScript bundle. Server-only variables like CALORIE_API_KEY are available in route handlers and never sent to clients.

← Back to all articles

Start building with the Calorie API

Get a free API key and access 4M+ foods with search, barcode lookup, and full macro data.