Project Goals
3 min- Build a Flask backend with a
/chatJSON endpoint. - Serve a simple chat web page (HTML + a little JS).
- Keep the API key on the SERVER, never in the browser.
- Maintain conversation history per session.
Warm-Up · Why a Backend?
5 minbrowser → Flask backend → Claude API (no key) (holds the key) (does the thinking)
NEVER call the LLM API from browser JavaScript — your key would be visible to everyone and anyone could spend your money. The browser talks to YOUR server; your server holds the key and calls Claude. This is the #1 security rule for LLM web apps.
Plan · Backend + Frontend
14 minFile tree
chatapp/ ├─ app.py # Flask + Claude ├─ templates/ │ └─ index.html # chat page + JS └─ .env # ANTHROPIC_API_KEY (git-ignored)
The backend endpoint
from flask import Flask, render_template, request, jsonify, session import anthropic app = Flask(__name__) app.secret_key = "dev-secret" client = anthropic.Anthropic() # key from env @app.route("/") def home(): session["history"] = [] # fresh chat return render_template("index.html") @app.route("/chat", methods=["POST"]) def chat(): user_msg = request.json["message"] history = session.get("history", []) history.append({"role": "user", "content": user_msg}) msg = client.messages.create( model="claude-haiku-4-5", max_tokens=400, system="You are a friendly, concise study buddy.", messages=history, ) reply = msg.content[0].text history.append({"role": "assistant", "content": reply}) session["history"] = history return jsonify({"reply": reply}) if __name__ == "__main__": app.run(debug=True)
The frontend (minimal JS)
<!-- templates/index.html -->
<div id="chat"></div>
<input id="msg" placeholder="Ask me anything...">
<button onclick="send()">Send</button>
<script>
async function send() {
const input = document.getElementById("msg");
const text = input.value; input.value = "";
add("You", text);
const res = await fetch("/chat", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({message: text}),
});
const data = await res.json();
add("AI", data.reply);
}
function add(who, text) {
document.getElementById("chat").innerHTML +=
"<p><b>" + who + ":</b> " + text + "</p>";
}
</script>Build · The Full App
12 min# app.py — complete chat backend import os from flask import Flask, render_template, request, jsonify, session import anthropic from dotenv import load_dotenv load_dotenv() app = Flask(__name__) app.secret_key = os.environ.get("FLASK_SECRET", "dev-secret") client = anthropic.Anthropic() SYSTEM = "You are a friendly study buddy for teens. Keep replies under 100 words." @app.route("/") def home(): session["history"] = [] return render_template("index.html") @app.route("/chat", methods=["POST"]) def chat(): user_msg = (request.json or {}).get("message", "").strip() if not user_msg: return jsonify({"reply": "Please type something!"}), 400 history = session.get("history", []) history.append({"role": "user", "content": user_msg}) history = history[-12:] # cap context for cost try: msg = client.messages.create( model="claude-haiku-4-5", max_tokens=400, system=SYSTEM, messages=history, ) reply = msg.content[0].text except anthropic.APIError as e: return jsonify({"reply": f"(error: {e})"}), 502 history.append({"role": "assistant", "content": reply}) session["history"] = history return jsonify({"reply": reply}) if __name__ == "__main__": app.run(debug=True)
Run it
$ python app.py * Running on http://localhost:5000/ → open the page, type a message, get a reply from Claude in your browser
Read the diff
Pure Level-4 Flask (routes, JSON, session, templates) wrapping a Level-5 LLM call. Three production touches: key from .env on the server, history capped to 12 messages (cost control), and a try/except returning a friendly error. The browser never sees the key. This is a genuine, shippable AI web app.
Extensions
13 minAdd CSS: chat bubbles, your messages right-aligned, AI left-aligned, auto-scroll to the newest.
Add a dropdown (tutor / pirate / poet) that changes the system prompt for the session.
Use Flask's Response with a generator + Server-Sent Events to stream the reply token-by-token to the page. (Harder — research SSE.)
Stretch · Deploy It
8 minDeploy your chat app to a free host (Render/Fly, like Level 4's L4-44). Set ANTHROPIC_API_KEY and FLASK_SECRET as host environment variables — never commit them. Add basic rate-limiting so a stranger can't run up your bill. Share the URL.
Recap
3 minBrowser → Flask → Claude. The key lives on the server only. The /chat endpoint maintains per-session history (capped for cost), wraps errors, and returns JSON the page renders. You combined every skill from Levels 4 and 5 into a live AI web app. Next: ground the model in your own documents with RAG.
Homework
4 minFinish your chat app: styled bubbles, a persona picker, history cap, friendly errors, key on the server. Bonus: deploy it. Write a short README covering setup and the one security rule (key never in the browser).
Extend app.py + index.html from the worked example. The README must state the key-stays-on-server rule — it's the most common beginner security mistake.