FastAPI + Vue 3 实战:从零搭建前后端分离管理系统

举报
yd_228838069 发表于 2026/11/02 09:36:32 2026/11/02
【摘要】 前面我们学习了 FastAPI 的基础使用、数据库操作、用户认证、分页搜索以及后台管理接口的开发。本篇将进一步整合前后端技术,从零搭建一个可以实际运行的前后端分离管理系统。通过本教程,你将学会如何使用 FastAPI 提供 REST API,使用 Vue 3 构建管理界面,并通过 HTTP 请求完成数据交互。本篇使用用户管理作为示例,最终实现以下功能:前端与后端分离部署Vue 3 管理页面F...

前面我们学习了 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> 语法,实现了以下功能:

  1. 页面加载时自动请求用户列表。

  2. 点击“新增用户”显示表单。

  3. 点击“编辑”加载当前用户的数据。

  4. 提交表单时调用新增或修改接口。

  5. 点击“删除”前进行二次确认。

  6. 请求失败时显示错误信息。

前端表单校验主要用于改善用户体验。后端仍然必须独立验证所有请求数据,不能相信浏览器提交的内容。

七、添加页面样式

编辑 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

打开浏览器,即可查看用户管理页面。

可以按以下顺序测试:

  1. 页面加载后,确认是否出现 Alice 和 Bob 两条示例数据。

  2. 新增用户,检查列表是否刷新。

  3. 编辑用户邮箱,确认修改是否生效。

  4. 删除一个用户,检查列表是否更新。

  5. 停止后端,再操作页面,观察错误提示是否正常显示。

如果以上步骤正常,说明前后端已经完成基本联调。

注意,示例使用内存列表保存数据,所以新增或修改的数据只在当前后端进程生命周期内有效。重启服务后,示例数据会重新初始化。

九、如何理解前后端之间的数据交互?

以新增用户为例,整个流程如下:

用户填写表单
      |
      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 请求与数据联调的完整流程。

需要记住的是,前后端分离是一种系统架构方式,而不是自动获得安全性或可扩展性的方法。真正的生产系统还需要数据库持久化、身份认证、权限控制、输入验证、日志审计、自动化测试和规范的部署流程。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。