跪拜 Guibai
← All articles
Frontend

Stop Exposing API Keys: A BFF Pattern for AI Streaming Apps

By 嘟嘟0717 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Shipping an AI feature that calls an LLM from the browser leaks the API key to anyone who opens DevTools. A thin Node BFF is the smallest safe pattern, and frontend engineers who can build it themselves ship faster without depending on backend capacity.

Summary

Frontend code that calls an LLM API directly exposes the secret key, forces every client to reimplement SSE stream parsing, and hits browser cross-origin blocks. Moving those concerns into a Node-based Backend For Frontend layer solves all three at once. The API key lives in a server-side `.env` file, the Express server digests raw SSE chunks into clean JSON, and Vite's dev proxy makes the browser see a same-origin request.

The walkthrough uses Vue 3, Vite, and Express to show the complete request path: a browser `fetch` to `/api/stream` gets rewritten and forwarded internally to Express on a different port, which then calls DeepSeek with the hidden key. Every step is mapped, from the file relay chain that loads a Vue SPA to the exact proxy configuration that keeps cross-origin checks out of the picture.

The article stops at the request plumbing; actual streaming response handling is promised as a follow-up. Even so, the skeleton demonstrates the core full-stack frontend skill of owning the entire chain from UI to AI provider without waiting for a separate backend team.

Takeaways
An API key placed in client-side JavaScript is visible to any user via browser DevTools and will be stolen.
Raw SSE streams require manual ReadableStream parsing, line splitting, and JSON extraction that must be duplicated on every client platform.
Different ports on localhost count as different origins; the browser blocks the request unless a proxy or CORS headers are used.
A BFF is a Node server written by the frontend team that sits between the browser and the LLM API.
Vite's dev-server proxy rewrites `/api/*` requests and forwards them to the Express BFF as Node-to-Node traffic, bypassing the browser's same-origin policy entirely.
The Express server reads the API key from a `.env` file via `dotenv`, so the key never reaches the browser.
The frontend calls a relative path like `/api/stream?prompt=hello`; Vite auto-completes it to the same origin and the proxy handles the rest.
Two `.then()` calls on a `fetch` are needed because the first resolves the Response envelope and the second parses the body JSON.
The article covers only the request plumbing; streaming the LLM response back to the browser in real time is left for a future post.
Conclusions

The architecture treats the BFF as a frontend-owned concern, which flips the usual backend-as-gatekeeper model and lets UI engineers control their own API surface.

Vite's proxy is presented as a development convenience, but the same pattern in production would require either a reverse proxy like Nginx or deploying the BFF on the same domain to avoid cross-origin issues entirely.

The walkthrough deliberately stops before implementing the streaming response, which is the harder problem; the current code only proves the request path works, not that a real-time chat experience is in place.

Concepts & terms
BFF (Backend For Frontend)
A dedicated server-side layer, often built by frontend engineers, that sits between a client application and downstream services. It handles concerns like authentication, data aggregation, and protocol translation so each frontend gets exactly the API shape it needs.
SSE (Server-Sent Events)
A standard that allows a server to push a stream of text events to a browser over a single HTTP connection. LLM APIs use it to deliver tokens as they are generated, rather than waiting for the full response.
Vite Proxy
A development-server feature that intercepts requests matching a configured path prefix and forwards them to another server. It runs inside the Node process, so the browser sees a same-origin request and cross-origin blocking does not apply.
ReadableStream
A Web API representing a source of data that can be read chunk by chunk. SSE responses arrive as a ReadableStream that must be manually decoded, split, and parsed to extract the event payloads.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗