FastAPI + Vue 3 实战:从零搭建前后端分离管理系统
前面我们学习了 FastAPI 的基础使用、数据库操作、用户认证、分页搜索以及后台管理接口的开发。本篇将进一步整合前后端技术,从零搭建一个可以实际运行的前后端分离管理系统。
通过本教程,你将学会如何使用 FastAPI 提供 REST API,使用 Vue 3 构建管理界面,并通过 HTTP 请求完成数据交互。
本篇使用用户管理作为示例,最终实现以下功能:
-
前端与后端分离部署
-
Vue 3 管理页面
-
FastAPI REST API
-
用户列表查询
-
新增、修改和删除用户
-
跨域请求配置
-
前后端联调与常见问题排查
一、什么是前后端分离?
传统 Web 应用通常由后端负责处理业务逻辑,同时生成 HTML 页面并返回给浏览器。
前后端分离则采用不同的开发方式:
-
前端: 负责页面展示、用户交互和数据呈现。
-
后端: 负责业务逻辑、数据处理、身份认证和 API 接口。
-
数据库: 负责持久化存储业务数据。
整体架构如下:
用户浏览器
|
v
Vue 3 + Vite 前端
|
| HTTP / JSON
v
FastAPI 后端
|
| SQL / ORM
v
MySQL 数据库
前端通过 HTTP 请求调用后端接口,后端返回 JSON 数据,前端根据返回结果更新页面。
这种架构可以让前后端独立开发、测试和部署,也方便后续扩展移动端、桌面端或其他 API 客户端。
二、准备开发环境
开始之前,需要安装以下工具:
| 工具 | 用途 |
|---|---|
| Python 3.11 或更高版本 | 运行 FastAPI |
| Node.js LTS | 运行前端开发工具 |
| npm | 安装前端依赖 |
| FastAPI | 提供后端接口 |
| Vue 3 | 构建前端界面 |
| Vite | 前端开发服务器和构建工具 |
| SQLAlchemy | 后端数据库 ORM |
| MySQL | 持久化存储 |
本篇先使用内存数据演示前后端通信和 CRUD 流程,以减少环境配置工作。正式项目可在后续步骤中接入 MySQL。
三、创建项目目录
新建一个项目目录:
fastapi-vue-admin/
├── backend/
│ ├── main.py
│ └── requirements.txt
└── frontend/
├── package.json
├── vite.config.js
└── src/
├── main.js
├── App.vue
└── style.css
其中:
-
backend/存放 FastAPI 后端。 -
frontend/存放 Vue 3 前端。 -
main.py定义 API 接口。 -
App.vue实现用户管理界面。 -
vite.config.js配置前端开发服务器和 API 代理。
四、搭建 FastAPI 后端
1. 创建 Python 虚拟环境
进入后端目录:
cd fastapi-vue-admin/backend
python -m venv .venv
Linux 或 macOS 激活环境:
source .venv/bin/activate
Windows 激活环境:
.venv\Scripts\Activate.ps1
创建 requirements.txt:
fastapi
uvicorn[standard]
安装依赖:
pip install -r requirements.txt
2. 编写后端 API
创建 main.py:
from itertools import count
from fastapi import FastAPI, HTTPException, Query, Response
from pydantic import BaseModel, Field
app = FastAPI(
title="用户管理系统 API",
version="1.0.0",
)
user_ids = count(1)
users = [
{
"id": next(user_ids),
"username": "alice",
"email": "alice@example.com",
"is_active": True,
},
{
"id": next(user_ids),
"username": "bob",
"email": "bob@example.com",
"is_active": True,
},
]
class UserCreate(BaseModel):
username: str = Field(min_length=2, max_length=50)
email: str = Field(max_length=254)
is_active: bool = True
class UserUpdate(BaseModel):
username: str | None = Field(default=None, min_length=2, max_length=50)
email: str | None = Field(default=None, max_length=254)
is_active: bool | None = None
@app.get("/api/users")
def list_users(
page: int = Query(default=1, ge=1),
page_size: int = Query(default=10, ge=1, le=100),
):
total = len(users)
start = (page - 1) * page_size
return {
"items": users[start:start + page_size],
"total": total,
"page": page,
"page_size": page_size,
}
@app.post("/api/users", status_code=201)
def create_user(data: UserCreate):
if any(user["username"] == data.username for user in users):
raise HTTPException(status_code=409, detail="用户名已存在")
user = {
"id": next(user_ids),
**data.model_dump(),
}
users.append(user)
return user
@app.put("/api/users/{user_id}")
def update_user(user_id: int, data: UserUpdate):
user = next(
(item for item in users if item["id"] == user_id),
None,
)
if user is None:
raise HTTPException(status_code=404, detail="用户不存在")
changes = data.model_dump(exclude_unset=True)
for key, value in changes.items():
if value is None:
raise HTTPException(
status_code=422,
detail=f"{key} 不能为 null",
)
user[key] = value
return user
@app.delete("/api/users/{user_id}", status_code=204)
def delete_user(user_id: int):
user = next(
(item for item in users if item["id"] == user_id),
None,
)
if user is None:
raise HTTPException(status_code=404, detail="用户不存在")
users.remove(user)
return Response(status_code=204)
这里定义了四类接口:
| 请求方式 | 接口 | 功能 |
|---|---|---|
| GET | /api/users |
获取用户列表 |
| POST | /api/users |
新增用户 |
| PUT | /api/users/{user_id} |
修改用户 |
| DELETE | /api/users/{user_id} |
删除用户 |
用户列表接口还支持分页参数,例如:
/api/users?page=1&page_size=10
后端通过 Pydantic 模型验证请求数据,使用 HTTP 状态码表示处理结果。
需要注意:当前数据存储在 Python 列表中,服务重启后数据会丢失。这只是用于演示接口联调的临时实现,不适合生产环境。
3. 启动后端
在 backend 目录执行:
uvicorn main:app --reload --host 127.0.0.1 --port 8000
启动后,可以访问:
-
API 地址:
http://127.0.0.1:8000 -
Swagger 文档:
http://127.0.0.1:8000/docs
在浏览器中打开 Swagger 文档,可以直接测试接口。
例如,访问:
http://127.0.0.1:8000/api/users
应该能够看到 JSON 格式的用户列表。
五、创建 Vue 3 前端项目
1. 使用 Vite 创建项目
在项目根目录执行:
npm create vite@latest frontend -- --template vue
进入前端目录:
cd frontend
npm install
安装完成后,Vite 会生成 Vue 3 项目的基础结构。
2. 配置开发代理
编辑 vite.config.js:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
server: {
host: '127.0.0.1',
port: 5173,
proxy: {
'/api': {
target: 'http://127.0.0.1:8000',
changeOrigin: true,
},
},
},
})
代理的作用是让前端开发服务器将 /api 请求转发给 FastAPI。
例如,前端请求:
http://127.0.0.1:5173/api/users
Vite 会将请求转发到:
http://127.0.0.1:8000/api/users
这样开发阶段就不必在每个请求中手动填写后端地址。
需要注意,Vite 代理只用于开发环境。生产环境应通过 Nginx 或其他反向代理配置 API 转发。
六、编写用户管理页面
编辑 src/App.vue:
<script setup>
import { onMounted, ref } from 'vue'
const users = ref([])
const loading = ref(false)
const errorMessage = ref('')
const showForm = ref(false)
const editingId = ref(null)
const form = ref({
username: '',
email: '',
is_active: true,
})
async function request(url, options = {}) {
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
})
if (!response.ok) {
const body = await response.json().catch(() => ({}))
throw new Error(body.detail || `请求失败:${response.status}`)
}
if (response.status === 204) {
return null
}
return response.json()
}
async function loadUsers() {
loading.value = true
errorMessage.value = ''
try {
const result = await request('/api/users')
users.value = result.items
} catch (error) {
errorMessage.value = error.message
} finally {
loading.value = false
}
}
function openCreateForm() {
editingId.value = null
form.value = {
username: '',
email: '',
is_active: true,
}
showForm.value = true
}
function openEditForm(user) {
editingId.value = user.id
form.value = {
username: user.username,
email: user.email,
is_active: user.is_active,
}
showForm.value = true
}
async function saveUser() {
errorMessage.value = ''
try {
if (editingId.value === null) {
await request('/api/users', {
method: 'POST',
body: JSON.stringify(form.value),
})
} else {
await request(`/api/users/${editingId.value}`, {
method: 'PUT',
body: JSON.stringify(form.value),
})
}
showForm.value = false
await loadUsers()
} catch (error) {
errorMessage.value = error.message
}
}
async function removeUser(user) {
if (!window.confirm(`确定删除用户 ${user.username} 吗?`)) {
return
}
try {
await request(`/api/users/${user.id}`, {
method: 'DELETE',
})
await loadUsers()
} catch (error) {
errorMessage.value = error.message
}
}
onMounted(loadUsers)
</script>
<template>
<main class="page">
<header class="header">
<div>
<h1>用户管理系统</h1>
<p>FastAPI + Vue 3 前后端分离实战</p>
</div>
<button class="primary" @click="openCreateForm">
新增用户
</button>
</header>
<p v-if="errorMessage" class="error">
{{ errorMessage }}
</p>
<section class="panel">
<p v-if="loading">正在加载数据...</p>
<table v-else>
<thead>
<tr>
<th>ID</th>
<th>用户名</th>
<th>邮箱</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="user in users" :key="user.id">
<td>{{ user.id }}</td>
<td>{{ user.username }}</td>
<td>{{ user.email }}</td>
<td>
{{ user.is_active ? '正常' : '停用' }}
</td>
<td>
<button @click="openEditForm(user)">
编辑
</button>
<button class="danger" @click="removeUser(user)">
删除
</button>
</td>
</tr>
<tr v-if="users.length === 0">
<td colspan="5">暂无用户数据</td>
</tr>
</tbody>
</table>
</section>
<section v-if="showForm" class="panel form-panel">
<h2>{{ editingId === null ? '新增用户' : '编辑用户' }}</h2>
<form @submit.prevent="saveUser">
<label>
用户名
<input
v-model.trim="form.username"
minlength="2"
maxlength="50"
required
/>
</label>
<label>
邮箱
<input
v-model.trim="form.email"
type="email"
maxlength="254"
required
/>
</label>
<label class="checkbox">
<input v-model="form.is_active" type="checkbox" />
启用用户
</label>
<div class="actions">
<button class="primary" type="submit">保存</button>
<button type="button" @click="showForm = false">
取消
</button>
</div>
</form>
</section>
</main>
</template>
这个页面使用 Vue 3 的 Composition API 和 <script setup> 语法,实现了以下功能:
-
页面加载时自动请求用户列表。
-
点击“新增用户”显示表单。
-
点击“编辑”加载当前用户的数据。
-
提交表单时调用新增或修改接口。
-
点击“删除”前进行二次确认。
-
请求失败时显示错误信息。
前端表单校验主要用于改善用户体验。后端仍然必须独立验证所有请求数据,不能相信浏览器提交的内容。
七、添加页面样式
编辑 src/style.css:
* {
box-sizing: border-box;
}
body {
margin: 0;
background: #f5f7fb;
color: #202938;
font-family: Arial, "Microsoft YaHei", sans-serif;
}
button,
input {
font: inherit;
}
button {
padding: 8px 14px;
border: 1px solid #d5dbe5;
border-radius: 6px;
background: white;
cursor: pointer;
}
button:hover {
opacity: 0.85;
}
.page {
max-width: 1100px;
margin: 40px auto;
padding: 0 20px;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
margin-bottom: 24px;
}
.header h1 {
margin-bottom: 8px;
}
.header p {
color: #667085;
}
.panel {
margin-bottom: 24px;
padding: 24px;
border: 1px solid #e4e8ef;
border-radius: 10px;
background: white;
}
table {
width: 100%;
border-collapse: collapse;
text-align: left;
}
th,
td {
padding: 14px 10px;
border-bottom: 1px solid #edf0f4;
}
.primary {
border-color: #2563eb;
background: #2563eb;
color: white;
}
.danger {
margin-left: 8px;
color: #dc2626;
}
.form-panel {
max-width: 560px;
}
form,
form label {
display: flex;
flex-direction: column;
gap: 10px;
}
form {
gap: 18px;
}
form input:not([type="checkbox"]) {
width: 100%;
padding: 10px;
border: 1px solid #d5dbe5;
border-radius: 6px;
}
.checkbox {
flex-direction: row;
align-items: center;
}
.actions {
display: flex;
gap: 10px;
}
.error {
padding: 12px;
border-radius: 6px;
background: #fef2f2;
color: #b91c1c;
}
@media (max-width: 640px) {
.header {
align-items: flex-start;
flex-direction: column;
}
.panel {
overflow-x: auto;
padding: 12px;
}
table {
min-width: 600px;
}
}
然后编辑 src/main.js,确保加载 Vue 应用和样式:
import { createApp } from 'vue'
import App from './App.vue'
import './style.css'
createApp(App).mount('#app')
八、启动前端并完成联调
确保后端仍在运行,然后在 frontend 目录执行:
npm run dev
Vite 默认会在以下地址启动开发服务器:
http://127.0.0.1:5173
打开浏览器,即可查看用户管理页面。
可以按以下顺序测试:
-
页面加载后,确认是否出现 Alice 和 Bob 两条示例数据。
-
新增用户,检查列表是否刷新。
-
编辑用户邮箱,确认修改是否生效。
-
删除一个用户,检查列表是否更新。
-
停止后端,再操作页面,观察错误提示是否正常显示。
如果以上步骤正常,说明前后端已经完成基本联调。
注意,示例使用内存列表保存数据,所以新增或修改的数据只在当前后端进程生命周期内有效。重启服务后,示例数据会重新初始化。
九、如何理解前后端之间的数据交互?
以新增用户为例,整个流程如下:
用户填写表单
|
v
Vue 3 收集表单数据
|
v
发送 POST /api/users
|
v
FastAPI 接收 JSON
|
v
Pydantic 校验请求
|
v
创建用户记录
|
v
返回 JSON 响应
|
v
Vue 3 重新加载列表
前端负责发起请求和更新界面,后端负责验证数据和执行业务逻辑。
这种分工让业务代码更加清晰,也便于未来替换前端框架,而不必完全重写后端接口。
十、生产环境需要注意哪些问题?
上面的项目用于教学,不能直接当作生产系统部署。正式上线前至少还需要完善以下内容。
1. 接入 MySQL
将内存列表替换为 MySQL 数据库,使用 SQLAlchemy 管理数据模型、事务和数据库连接池。
2. 增加用户认证
为管理接口添加登录功能,并通过服务端认证与权限校验控制访问。不能因为前端没有显示某个按钮,就认为用户无法调用对应接口。
3. 配置跨域策略
本篇使用 Vite 代理,因此开发阶段不需要单独配置 CORS。
如果生产环境前端和后端使用不同的源,则应在 FastAPI 中配置 CORSMiddleware,明确允许的来源、方法和请求头。启用凭据时,不应将允许来源设置为任意来源。
4. 统一 API 响应和异常处理
为接口定义一致的数据格式,合理使用 400、401、403、404、409、422 和 500 等状态码,避免将所有错误都转换成相同的响应。
5. 添加分页、搜索与筛选
当前示例仅实现简单分页。真实项目应在数据库层完成分页和筛选,避免一次性加载全部记录。
6. 管理环境变量
数据库密码、JWT 密钥和其他敏感配置应放在环境变量或专门的密钥管理系统中,不应提交到公开代码仓库。
7. 配置生产部署
生产环境可以使用 Nginx 代理前端静态文件和 /api/ 请求,FastAPI 则由 Uvicorn 等 ASGI 服务器运行,并通过 systemd 或容器管理进程。
8. 增加测试与日志
为关键 API 编写自动化测试,记录重要业务事件,并避免在日志中输出密码、访问令牌等敏感数据。
十一、总结
本篇使用 FastAPI 和 Vue 3 搭建了一个简单的前后端分离用户管理系统,实现了列表查询、新增、编辑和删除等基础功能。
通过这次实战,我们完成了从后端接口定义、前端页面开发,到 HTTP 请求与数据联调的完整流程。
需要记住的是,前后端分离是一种系统架构方式,而不是自动获得安全性或可扩展性的方法。真正的生产系统还需要数据库持久化、身份认证、权限控制、输入验证、日志审计、自动化测试和规范的部署流程。
- 点赞
- 收藏
- 关注作者
评论(0)