A6.0 What This Teaches
- Flask basics - creating an app, defining routes, running the dev server
- Serving HTML with
render_template() - Handling POST routes that accept JSON request bodies
- Calling the Anthropic API from a web server context
- Returning JSON from Flask with
jsonify() - Maintaining in-memory conversation history across requests
A6.1 Application Layout
web_chat/
├── app.py ← Flask server
└── templates/
└── index.html ← chat UI
templates/ folder by default.
That folder must sit next to app.py.
A6.2 The Flask Server
- GET / - returns the chat page HTML
- POST /chat - receives a message, calls Claude, returns the reply as JSON
messages.
This is intentionally simple: it supports one user at a time and resets when the
server restarts. That is fine for local development and learning.
# app.py (structure) - Flask server with in-memory chat history.
from flask import Flask, render_template, request, jsonify
import anthropic
app = Flask(__name__)
client = anthropic.Anthropic()
messages = [] # single-user, in-memory history; resets on restart
@app.route("/")
def index():
return render_template("index.html")
@app.route("/chat", methods=["POST"])
def chat():
user_text = request.json["message"]
messages.append({"role": "user", "content": user_text})
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=messages,
)
reply = response.content[0].text
messages.append({"role": "assistant", "content": reply})
return jsonify({"reply": reply})
A6.3 Complete app.py
# app.py - Flask chat server backed by Claude.
# Run: python app.py, then open http://127.0.0.1:5000
from flask import Flask, render_template, request, jsonify
import anthropic
app = Flask(__name__)
client = anthropic.Anthropic()
# In-memory history - single-user only, resets on server restart.
messages = []
@app.route("/")
def index():
return render_template("index.html")
@app.route("/chat", methods=["POST"])
def chat():
data = request.json
if not data or not data.get("message"):
return jsonify({"error": "empty message"}), 400
user_text = data["message"]
messages.append({"role": "user", "content": user_text})
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=messages,
)
reply = response.content[0].text
messages.append({"role": "assistant", "content": reply})
return jsonify({"reply": reply})
if __name__ == "__main__":
app.run(debug=True)
debug=True enables auto-reload on file changes. Turn it off in any
non-development environment.
A6.4 The HTML Template
- A scrollable
divthat shows the conversation - An
inputfield where the user types - A Send button that triggers a
fetch()POST to/chat
// Sends a message and appends the reply to the chat log.
async function sendMessage() {
const input = document.getElementById('user-input');
const text = input.value.trim();
if (!text) return;
input.value = '';
appendMessage('You', text);
const res = await fetch('/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: text }),
});
const data = await res.json();
appendMessage('Claude', data.reply);
}
A6.5 Complete index.html
<!-- templates/index.html - Chat UI -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Web Chat</title>
<style>
body { font-family: sans-serif; max-width: 700px; margin: 2rem auto; }
#chat-log { border: 1px solid #ccc; height: 400px; overflow-y: auto;
padding: 1rem; margin-bottom: 1rem; }
.msg { margin-bottom: 0.75rem; }
.msg strong { display: block; }
#controls { display: flex; gap: 0.5rem; }
#user-input { flex: 1; padding: 0.4rem; }
</style>
</head>
<body>
<h1>Web Chat</h1>
<div id="chat-log"></div>
<div id="controls">
<input id="user-input" type="text" placeholder="Type a message..."
onkeydown="if(event.key==='Enter') sendMessage()">
<button onclick="sendMessage()">Send</button>
</div>
<script>
function appendMessage(who, text) {
const log = document.getElementById('chat-log');
const div = document.createElement('div');
div.className = 'msg';
div.innerHTML = '<strong>' + who + '</strong>' + text;
log.appendChild(div);
log.scrollTop = log.scrollHeight;
}
async function sendMessage() {
const input = document.getElementById('user-input');
const text = input.value.trim();
if (!text) return;
input.value = '';
appendMessage('You', text);
const res = await fetch('/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: text }),
});
const data = await res.json();
appendMessage('Claude', data.reply);
}
</script>
</body>
</html>
A6.6 Running the Application
pip install flask anthropic
cd web_chat
python app.py
http://127.0.0.1:5000 in a browser. Type a message and press
Enter or click Send. The reply appears below your message without a page reload.
* Running on http://127.0.0.1:5000
* Debug mode: on
* Restarting with stat
A6.7 Exercise
Exercise
Add a "Clear" button that resets the conversation on both the server and the browser.
Test it: have a multi-turn conversation, click Clear, then send a new message.
Confirm the model no longer remembers the earlier turns.
- Add a POST route
/clearinapp.pythat runsmessages.clear()and returnsjsonify({"ok": True}). - Add a Clear button to
index.htmlthat callsfetch('/clear', {method: 'POST'})and then clears the#chat-logdiv.
A6.8 Common Mistakes
Storing history in a module-level list for multiple users
messages list works correctly for one user at a time
during development. With two simultaneous users, their messages get interleaved
and both users see the wrong history. For production, use a session or database
keyed per user.
Returning HTML from the /chat route instead of JSON
jsonify({"reply": text}) lets the JS
read data.reply directly - much simpler and less error-prone.
Forgetting Content-Type: application/json in fetch()
headers: {'Content-Type': 'application/json'} in the
fetch() call, Flask receives the body as raw bytes and
request.json returns None. Always set the header when
sending a JSON body.
Key Terms
| Term | Meaning |
|---|---|
| Flask | Lightweight Python web framework; maps URLs to Python functions called routes |
| route | A URL pattern bound to a Python function with @app.route() |
| render_template | Flask helper that reads an HTML file from the templates/ folder and returns it |
| request.json | The parsed JSON body of an incoming POST request; None if Content-Type is wrong |
| jsonify | Flask helper that converts a Python dict to a JSON HTTP response with correct headers |
| fetch() | Browser API for making HTTP requests from JavaScript without a page reload |
| POST | HTTP method for sending data to the server; used here to submit chat messages |
| in-memory session | Conversation history stored in a Python list; fast but lost on server restart |