ChatGPT 代码示例:从 Python 到前端实战演示
下面给你一套“从 Python 到前端”的完整可跑实战示例:
- Python(FastAPI):提供 API + 简单校验 + 错误处理
- 前端(纯 HTML/JS):调用 API、渲染结果
- 额外再给一个 React 版组件(同样调用同一 API)
实战目标
做一个“Todo 列表”的小应用:
GET /todos:获取列表POST /todos:新增 todoDELETE /todos/{id}:删除 todo
1)Python 后端(FastAPI)代码
1.1 安装
python -m venv .venv
# Windows:
# .venv\Scripts\activate
# macOS/Linux:
# source .venv/bin/activate
pip install fastapi uvicorn pydantic
1.2 保存为 main.py
from typing import List, Optional
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
app = FastAPI(title="Todo API", version="1.0.0")
class TodoCreate(BaseModel):
title: str = Field(..., min_length=1, max_length=120)
class Todo(BaseModel):
id: int
title: str
done: bool = False
# 简化:用内存存储(真实项目换数据库)
TODOS: List[Todo] = []
NEXT_ID = 1
@app.get("/health")
def health():
return {"status": "ok"}
@app.get("/todos", response_model=list[Todo])
def list_todos(q: Optional[str] = None):
if q:
q_lower = q.lower()
return [t for t in TODOS if q_lower in t.title.lower()]
return TODOS
@app.post("/todos", response_model=Todo, status_code=201)
def create_todo(payload: TodoCreate):
global NEXT_ID
title = payload.title.strip()
if not title:
raise HTTPException(status_code=400, detail="title cannot be empty")
todo = Todo(id=NEXT_ID, title=title, done=False)
NEXT_ID += 1
TODOS.append(todo)
return todo
@app.delete("/todos/{todo_id}", status_code=204)
def delete_todo(todo_id: int):
idx = next((i for i, t in enumerate(TODOS) if t.id == todo_id), None)
if idx is None:
raise HTTPException(status_code=404, detail="todo not found")
TODOS.pop(idx)
return None
1.3 运行
uvicorn main:app --reload --port 8000
打开:
http://127.0.0.1:8000/healthhttp://127.0.0.1:8000/docs(Swagger)
2)前端(纯 HTML + JS)实战:调用 API 并渲染
保存为
index.html,用浏览器打开即可(推荐用 VSCode Live Server 或任意静态服务器打开,避免某些浏览器跨域限制)。
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Todo Demo</title>
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial; max-width: 720px; margin: 40px auto; padding: 0 16px; }
h1 { margin-bottom: 10px; }
form { display: flex; gap: 8px; margin: 12px 0 16px; }
input { flex: 1; padding: 10px; font-size: 16px; }
button { padding: 10px 14px; font-size: 16px; cursor: pointer; }
ul { padding-left: 18px; }
li { display: flex; align-items: center; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #eee; }
.meta { color: #666; font-size: 13px; }
.error { color: #b00020; margin-top: 10px; }
</style>
</head>
<body>
<h1>Todo Demo</h1>
<div class="meta">后端 API:<code id="apiBase"></code></div>
<form id="todoForm">
<input id="titleInput" placeholder="输入一个 todo,比如:学习 FastAPI" />
<button type="submit">添加</button>
</form>
<div>
<button id="refreshBtn" type="button">刷新列表</button>
</div>
<ul id="todoList"></ul>
<div id="error" class="error"></div>
<script>
const API_BASE = "http://127.0.0.1:8000";
document.getElementById("apiBase").textContent = API_BASE;
const todoList = document.getElementById("todoList");
const errorBox = document.getElementById("error");
const titleInput = document.getElementById("titleInput");
function setError(msg) {
errorBox.textContent = msg || "";
}
async function fetchJSON(url, options = {}) {
const res = await fetch(url, {
...options,
headers: {
"Content-Type": "application/json",
...(options.headers || {}),
},
});
// 204 No Content
if (res.status === 204) return null;
const text = await res.text();
const data = text ? JSON.parse(text) : null;
if (!res.ok) {
const detail = data?.detail || `HTTP ${res.status}`;
throw new Error(detail);
}
return data;
}
function renderTodos(todos) {
todoList.innerHTML = "";
for (const t of todos) {
const li = document.createElement("li");
const left = document.createElement("div");
left.textContent = `#${t.id} ${t.title}`;
const delBtn = document.createElement("button");
delBtn.textContent = "删除";
delBtn.addEventListener("click", async () => {
try {
setError("");
await fetchJSON(`${API_BASE}/todos/${t.id}`, { method: "DELETE" });
await loadTodos();
} catch (e) {
setError(e.message);
}
});
li.appendChild(left);
li.appendChild(delBtn);
todoList.appendChild(li);
}
}
async function loadTodos() {
try {
setError("");
const todos = await fetchJSON(`${API_BASE}/todos`);
renderTodos(todos);
} catch (e) {
setError(e.message);
}
}
document.getElementById("todoForm").addEventListener("submit", async (ev) => {
ev.preventDefault();
try {
setError("");
const title = titleInput.value;
await fetchJSON(`${API_BASE}/todos`, {
method: "POST",
body: JSON.stringify({ title }),
});
titleInput.value = "";
await loadTodos();
} catch (e) {
setError(e.message);
}
});
document.getElementById("refreshBtn").addEventListener("click", loadTodos);
// 初次加载
loadTodos();
</script>
</body>
</html>
3)React 版本(可选):同一个 API 的前端组件
假设你已经有 React 项目(Vite/Next 都行)。下面是一个最小可用组件。
import { useEffect, useState } from "react";
const API_BASE = "http://127.0.0.1:8000";
export default function TodoApp() {
const [title, setTitle] = useState("");
const [todos, setTodos] = useState([]);
const [error, setError] = useState("");
async function fetchJSON(url, options = {}) {
const res = await fetch(url, {
...options,
headers: { "Content-Type": "application/json", ...(options.headers || {}) },
});
if (res.status === 204) return null;
const text = await res.text();
const data = text ? JSON.parse(text) : null;
if (!res.ok) throw new Error(data?.detail || `HTTP ${res.status}`);
return data;
}
async function loadTodos() {
try {
setError("");
const data = await fetchJSON(`${API_BASE}/todos`);
setTodos(data);
} catch (e) {
setError(e.message);
}
}
async function addTodo(e) {
e.preventDefault();
try {
setError("");
await fetchJSON(`${API_BASE}/todos`, {
method: "POST",
body: JSON.stringify({ title }),
});
setTitle("");
await loadTodos();
} catch (e) {
setError(e.message);
}
}
async function delTodo(id) {
try {
setError("");
await fetchJSON(`${API_BASE}/todos/${id}`, { method: "DELETE" });
await loadTodos();
} catch (e) {
setError(e.message);
}
}
useEffect(() => {
loadTodos();
}, []);
return (
<div style={{ maxWidth: 720, margin: "40px auto", padding: 16, fontFamily: "system-ui" }}>
<h1>Todo Demo (React)</h1>
<div style={{ color: "#666", fontSize: 13 }}>
API: <code>{API_BASE}</code>
</div>
<form onSubmit={addTodo} style={{ display: "flex", gap: 8, marginTop: 12 }}>
<input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="输入 todo"
style={{ flex: 1, padding: 10, fontSize: 16 }}
/>
<button type="submit" style={{ padding: "10px 14px", fontSize: 16 }}>
添加
</button>
</form>
<button onClick={loadTodos} style={{ marginTop: 12, padding: "8px 12px" }}>
刷新
</button>
{error ? <div style={{ color: "#b00020", marginTop: 10 }}>{error}</div> : null}
<ul style={{ marginTop: 12 }}>
{todos.map((t) => (
<li key={t.id} style={{ display: "flex", justifyContent: "space-between", padding: "8px 0", borderBottom: "1px solid #eee" }}>
<span>#{t.id} {t.title}</span>
<button onClick={() => delTodo(t.id)}>删除</button>
</li>
))}
</ul>
</div>
);
}
4)常见问题(你跑不起来通常是这些原因)
- 跨域(CORS)报错
如果你前端不是同域访问后端,FastAPI 需要加 CORS。你可以在main.py加:
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 生产环境要收紧到你的域名
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
- 端口不一致
确认前端里的API_BASE和你 uvicorn 运行的端口一致。 - 打开 HTML 的方式
建议用本地静态服务器打开(比如 VSCode Live Server),更稳定。


