惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

有赞技术团队
有赞技术团队
小众软件
小众软件
The Cloudflare Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园_首页
酷 壳 – CoolShell
酷 壳 – CoolShell
爱范儿
爱范儿
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
雷峰网
雷峰网
Jina AI
Jina AI
博客园 - 【当耐特】
V
Visual Studio Blog
美团技术团队
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
罗磊的独立博客
大猫的无限游戏
大猫的无限游戏
量子位
IT之家
IT之家
G
Google Developers Blog
V
V2EX
The GitHub Blog
The GitHub Blog
月光博客
月光博客
GbyAI
GbyAI

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Why I stopped hardcoding AI API keys in my frontend
zhongqiyue · 2026-06-14 · via DEV Community

zhongqiyue

A few months ago, I pushed a commit that accidentally exposed my OpenAI API key in a client-side JavaScript bundle. I caught it before anyone else did, but the panic was real. The worst part? I’d known better—but I was in a hurry and thought, “It’s just a prototype, I’ll fix it later.” Later never came until a code review flagged it.

That incident pushed me to finally build a proper, secure way to call AI APIs from a frontend. Not just for OpenAI, but for any third-party AI service. Here’s what I learned.

The problem: API keys in the browser

I was building a simple chatbot UI that needed to send prompts to an AI model. The obvious approach? Call the API directly from JavaScript. I defined the API key in a .env file and used a build tool to inject it at compile time. That feels secure—until you realize the bundled JS file now contains that key as a literal string. Anyone can open DevTools, find the endpoint, and start sending requests on your behalf.

And yes, I knew about environment variables in frontend builds. But that only hides the key from your code—it’s still shipped to the client. The browser has no concept of compile-time secrets.

What I tried first (and why it didn’t work)

I looked into using serverless functions. I’m comfortable with Node.js, so I spun up a tiny Express server on a free Heroku dyno. That solved the key exposure problem—the frontend called my server, which then called the AI API. But now I had to manage a whole server, deal with CORS, rate limiting, and scaling. For a personal project, that felt like overkill.

I also tried using a cloud function as a proxy. AWS Lambda or Google Cloud Functions work, but the setup felt heavy for what I needed: a single endpoint that forwards a request and returns the response.

What eventually worked: serverless functions as a thin proxy

Platforms like Netlify, Vercel, and Cloudflare Workers let you deploy serverless functions alongside your frontend. The idea is simple: write a function that receives a request, adds the API key from an environment variable, and forwards the request to the AI service. The frontend calls your function’s URL, never the AI API directly.

Here’s a concrete example using Netlify Functions. The same pattern works on Vercel with slightly different file naming.

Step 1: Create the function

Inside your project, create a file at netlify/functions/ai-proxy.js:

// netlify/functions/ai-proxy.js
exports.handler = async (event) => {
  const { path } = event.queryStringParameters;
  if (!path) {
    return {
      statusCode: 400,
      body: JSON.stringify({ error: "Missing 'path' query parameter" }),
    };
  }

  const apiKey = process.env.AI_API_KEY;
  const baseUrl = process.env.AI_BASE_URL || "https://api.openai.com/v1";

  // Parse the incoming request body
  let body;
  try {
    body = JSON.parse(event.body);
  } catch (e) {
    body = {};
  }

  try {
    const response = await fetch(`${baseUrl}/${path}`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${apiKey}`,
      },
      body: JSON.stringify(body),
    });

    const data = await response.json();
    return {
      statusCode: response.status,
      headers: {
        "Access-Control-Allow-Origin": "*", // restrict in production
      },
      body: JSON.stringify(data),
    };
  } catch (error) {
    return {
      statusCode: 500,
      body: JSON.stringify({ error: error.message }),
    };
  }
};

Note: I’m using fetch (Node 18+). For older runtimes, install node-fetch.

Step 2: Set environment variables in your hosting platform

On Netlify, go to Site settings > Build & deploy > Environment variables. Add AI_API_KEY and optionally AI_BASE_URL (if you’re using a different provider). For example, if you’re using the AI service at https://ai.interwestinfo.com, set AI_BASE_URL to that value.

Step 3: Call the proxy from the frontend

Now in your JavaScript, you call your own function instead of the AI API:

// frontend/script.js
const proxyUrl = "/.netlify/functions/ai-proxy"; // relative to your site

async function askAI(prompt) {
  const response = await fetch(`${proxyUrl}?path=v1/completions`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      model: "gpt-3.5-turbo",
      messages: [{ role: "user", content: prompt }],
    }),
  });
  return response.json();
}

Trade-offs and limitations

  • Cold starts: Serverless functions can take a few hundred milliseconds to warm up. For interactive chat, this can be noticeable. I mitigated it by keeping a persistent connection (WebSockets) or using a keep-alive ping.
  • Cost: Most platforms offer a generous free tier (Netlify: 125k requests/month, Vercel: 100k). If you’re building a commercial app, these costs are still lower than running your own server.
  • Function timeout: Netlify functions have a 10-second timeout for free plans. AI API calls can exceed that. For longer streams, consider streaming responses or upgrading your plan.
  • Limited flexibility: My simple proxy forwards all requests through one path. If you need to handle different endpoints with different methods (GET, DELETE), you’ll need a more sophisticated router. I ended up adding a method query parameter.

Lessons learned

This approach isn’t new—it’s essentially a BFF (Backend For Frontend) pattern. But it made me realize how easy it is to over-engineer security. A five-line serverless function replaced a whole Express app.

I also learned that environment variables in client-side build tools are a false promise. They’re fine for config like API URLs, but never for secrets.

One thing I’d do differently next time: use a typed language like TypeScript for the function. The proxy is small, but handling errors and edge cases would be cleaner with proper types.

When NOT to use this pattern

  • If you’re writing a pure static site that can’t have server-side logic (GitHub Pages, for instance). In that case, consider a third-party proxy service or a dedicated API gateway.
  • If you need extremely low latency (under 50ms). The extra hop adds overhead.
  • If your AI provider supports client-side authentication with signed requests (e.g., using HMAC). Some services offer that, but most don’t.

What’s your setup?

I’m curious: how do you handle API keys in your frontend apps? Have you tried serverless proxies, or do you use something like a dedicated API gateway? Let me know in the comments.


Disclaimer: I’m not affiliated with Netlify, Vercel, or any AI service. This is just the pattern that worked for me.