Python API meets JavaScript fetch() — close the loop between your FastAPI backend and a live HTML interface.
Day 59 of 80
Option 1: Update Flask's app.py — Your existing Flask app can make requests to the FastAPI API using the requests library. Flask handles the HTML; FastAPI handles the data. This is a microservices pattern — two separate servers running at different ports.
Option 2: Pure HTML + JavaScript fetch() — A plain HTML file that talks directly to the FastAPI backend using browser-native fetch(). No Flask required. The browser is the frontend; FastAPI is the backend. This is the most common pattern for modern web apps.
Today you'll build Option 2. Marc already knows JavaScript, so fetch() is familiar territory — you're just learning to point it at a Python API.
Here's the full data flow:
Python (FastAPI) → JSON response → JavaScript fetch() → DOM update → HTML visible to user
FastAPI doesn't know or care what the frontend looks like. It just returns JSON. JavaScript doesn't know or care that the backend is Python. It just receives JSON. This separation is what makes REST APIs powerful — you can replace the frontend or the backend independently.
When a browser page at one origin (e.g., file://index.html) calls an API at another origin (localhost:8000), the browser blocks it by default. This is CORS — Cross-Origin Resource Sharing. Add this to api.py before you test the frontend:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI(title="DVP Prompt Vault API")
# Allow any origin in development — tighten this in production
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
["*"] with your actual frontend domain: ["https://dynamicvibe.net"]. For local development, wildcard is fine.index_api.htmlCreate this file in your project folder. It's a standalone HTML file — no server needed to serve it, just open it in your browser while api.py is running.
<!-- index_api.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DVP Prompt Vault</title>
<style>
body { font-family: var(--font-body, 'Hanken Grotesk', system-ui, sans-serif); max-width: 800px; margin: 40px auto; padding: 0 20px; }
.prompt-card { border: 1px solid #e2e8f0; border-radius: 8px; padding: 16px; margin: 12px 0; }
.platform-badge { background: #6366f1; color: white; padding: 2px 8px; border-radius: 4px; font-size: 12px; }
button { background: #6366f1; color: white; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; }
input, textarea { width: 100%; padding: 8px; margin: 4px 0 12px; border: 1px solid #e2e8f0; border-radius: 6px; }
</style>
</head>
<body>
<h1>DVP Prompt Vault</h1>
<section id="add-prompt">
<h2>Add Prompt</h2>
<input id="platform" placeholder="Platform (Kling, Runway, Veo...)">
<input id="shot" placeholder="Shot description">
<textarea id="prompt-text" rows="3" placeholder="Full prompt text"></textarea>
<button onclick="addPrompt()">Add Prompt</button>
</section>
<section id="prompts-list">
<h2>All Prompts <span id="count"></span></h2>
<div id="prompts">Loading...</div>
</section>
<script src="app.js"></script>
</body>
</html>
const API_BASE = 'http://localhost:8000';
// Fetch all prompts from the API and display them
async function loadPrompts() {
const response = await fetch(`${API_BASE}/prompts`);
const prompts = await response.json();
document.getElementById('count').textContent = `(${prompts.length})`;
displayPrompts(prompts);
}
function displayPrompts(prompts) {
const container = document.getElementById('prompts');
if (prompts.length === 0) {
container.innerHTML = '<p>No prompts yet.</p>';
return;
}
container.innerHTML = prompts.map(p => `
<div class="prompt-card">
<span class="platform-badge">${p.platform}</span>
<strong> ${p.shot}</strong>
<p>${p.prompt_text}</p>
<button onclick="deletePrompt(${p.id})">Delete</button>
</div>
`).join('');
}
// Create a new prompt via POST /prompts
async function addPrompt() {
const platform = document.getElementById('platform').value;
const shot = document.getElementById('shot').value;
const promptText = document.getElementById('prompt-text').value;
const response = await fetch(`${API_BASE}/prompts`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
platform,
shot,
prompt_text: promptText
})
});
if (response.ok) {
// Clear form fields
document.getElementById('platform').value = '';
document.getElementById('shot').value = '';
document.getElementById('prompt-text').value = '';
loadPrompts(); // refresh the list
} else {
const error = await response.json();
alert(`Error: ${error.detail}`);
}
}
// Delete a prompt via DELETE /prompts/{id}
async function deletePrompt(id) {
const response = await fetch(`${API_BASE}/prompts/${id}`, {
method: 'DELETE'
});
if (response.ok) {
loadPrompts();
}
}
// Load prompts when the page opens
loadPrompts();
addPrompt() fails (e.g., invalid platform name), response.ok is false. The error.detail is the message you put in HTTPException back in api.py. Your Python validation error flows cleanly to the JavaScript error handler./generate EndpointIf you finish early, add a /generate endpoint to api.py that calls Claude and returns a suggested prompt. This sets up exactly what you'll build in Week 13 with async.
import anthropic
from dotenv import load_dotenv
load_dotenv()
claude = anthropic.Anthropic()
class GenerateRequest(BaseModel):
shot: str
platform: str = "Kling"
@app.post("/generate")
def generate_prompt(data: GenerateRequest):
"""Ask Claude to generate a video prompt for a given shot and platform."""
message = claude.messages.create(
model="claude-sonnet-4-6",
max_tokens=200,
messages=[{
"role": "user",
"content": f"Write a {data.platform} AI video generation prompt for: {data.shot}. Under 75 words."
}]
)
return {
"shot": data.shot,
"platform": data.platform,
"generated_prompt": message.content[0].text
}
async def and use AsyncAnthropic() to generate for multiple platforms in parallel.api.py so the browser can call the APIindex_api.html and app.js — frontend that calls FastAPIloadPrompts() displays real data from the APIaddPrompt() — new prompts appear in the list immediately after addingdeletePrompt() — deleted prompts disappear from the listYou'll consolidate everything from this week — FastAPI routing, Pydantic validation, HTTP methods, and the frontend connection — before Week 13 takes your API async.