AI Feature Integration
Build AI features into the app — categorizers, chatbots, summarizers — always routing LLM calls through your backend.
The second way AI touches your work is the features you build into the app: chatbots, summarizers, smart categorization. From the frontend's perspective these are normal data interactions — you send some text to an endpoint and render the result — but with two twists: the call must never expose a provider key, and responses are often best streamed token by token.
A simple feature like an expense categorizer is just a POST to your own backend with the text to classify; the backend calls the LLM and returns a category. The frontend never touches the model or its key directly. This keeps the secret server-side and lets the backend apply guardrails (rate limits, prompt construction, input validation, content filtering) in one trusted place.
Chatbot-style features feel much faster when you stream the response instead of waiting for the full answer. The frontend reads the response body as a stream via the Streams API: get a reader from response.body, decode each chunk with a TextDecoder, and append it to state as tokens arrive. The UI updates incrementally — setResponse(prev => prev + chunk) — so the user sees text appear progressively, the same experience as a typing assistant.
The security pattern is the headline rule: never call an LLM provider directly from the browser with a secret key, because anything in the bundle is public. Route every AI call through your backend, which holds the key and enforces guardrails. This is true whichever provider you use — for example, calling Anthropic's Claude (such as claude-opus-4-8) belongs on the server, with the frontend talking only to your own /api/ai/* endpoints.
This is the classic backend-for-frontend / API gateway pattern. Just as you'd never let a browser hit your payment provider with the secret API key directly — you proxy through a server that holds the credential and validates the request — LLM calls go through your backend. The streaming response maps to server-sent events or a chunked HTTP response your Java/Vert.x layer would forward from the model.
- Never expose an LLM provider key in the browser — proxy all AI calls through your backend, which holds the key and applies guardrails.
- For an AI feature, the frontend just POSTs text to your own endpoint and renders the result; the model lives behind the backend.
- Stream chatbot-style responses with the Streams API (reader + TextDecoder) and append chunks to state so text appears progressively.
- Backend ownership of the call enables rate limiting, input validation, prompt construction, and content filtering in one trusted place.
Worked Code
// Building an AI feature: expense categorizer
async function categorizeExpense(description: string): Promise<string> {
// ALWAYS proxy through your backend — never expose API keys
const response = await api.post('/api/ai/categorize', {
description,
});
return response.data.category; // 'meals' | 'travel' | 'other'
}// Streaming AI responses (for chatbot-like features)
async function streamAIResponse(
question: string,
onChunk: (text: string) => void
) {
const response = await fetch('/api/ai/ask', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question }),
});
const reader = response.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
onChunk(decoder.decode(value));
}
}// Usage in component
function AIChatPanel() {
const [response, setResponse] = useState('');
const ask = async (question: string) => {
setResponse('');
await streamAIResponse(question, (chunk) => {
setResponse(prev => prev + chunk); // append as tokens arrive
});
};
return <div className="whitespace-pre-wrap">{response}</div>;
}Interview-Ready Q&A
From the frontend, you treat it as a normal request to your own backend: POST the input text to an endpoint like /api/ai/categorize and render the returned result. The backend is what actually calls the model (for example Anthropic's Claude, claude-opus-4-8) using the secret key it holds. This keeps the key off the client, and it lets the backend build the prompt, validate input, rate-limit, and filter output. The frontend stays thin — it never imports a provider SDK or knows the model name.
- 1Golden rule: never call an LLM provider from the browser with a secret key — proxy through your backend.
- 2An AI feature on the frontend is just a POST to your own /api/ai/* endpoint that returns the result.
- 3Stream responses with response.body.getReader() + TextDecoder, appending chunks to state for progressive output.