
简介面向正在学习FastAPI和Vue.js前后端分离开发的Python开发者这是一份可直接运行参考的完整项目源码模板能帮读者快速建立前后端接口联调与工程结构的整体概念。压缩包体积仅13KB共包含7个文件py文件提供后端主程序与核心逻辑html文件展示前端页面结构txt记录依赖清单inscode用于在线环境配置pyc为编译缓存文件类型虽少但覆盖了运行一个演示项目所需的关键模块。当前已有148人浏览学习压缩包不大但结构完整麻雀虽小五脏俱全适合直接运行和二次修改。通过阅读源码可以对照理解FastAPI的路由注册、依赖注入、CORS设置以及Vue组件与接口调用方式还能看到模板渲染和后续部署所需的配置要点是一份适合初学者边看边改的入门级全栈参考。1. 标题在讲什么一条命令跑起后端前端却在跨域里折腾一晚上“前后端分离”听着像架构问题实际落地时大多数人都卡在联调那一层FastAPI 在 8000 端口跑得好好的Vue 开发服务器在 5173 端口连过去要么 CORS 报错要么刷新就 404。这个标题给的是 FastAPI 与 Vue 前后端分离的完整项目源码能解决的就是这一整条链路——后端最小骨架怎么写、前端接口层怎么封装、开发阶段跨域怎么办、上线时两个工程怎么合到一起。适合刚接触前后端分离的读者也适合想把自己的单页 Demo 升级成双端架构、又不想从零摸索的工程师。下面按“先立住选型逻辑→照着源码搭骨架→打通联调→避开高频坑→往鉴权和部署走”的顺序来拆。2. 为什么是 FastAPI 和 Vue这一对组合的硬逻辑先讲透2.1 前后端分离的本质接口契约先于页面前后端分离不是把代码分成两个文件夹那么简单本质是把“数据怎么给”和“页面怎么渲染”彻底拆开。后端只负责提供接口前端只负责把拿到的数据变成界面。这意味着开发流程的第一件事不是画页面而是定接口契约。在 FastAPI 里这个契约被做成了一件非常直观的事写完一个路由函数框架会自动生成 OpenAPI 文档你不需要额外维护接口文档。而 Vue 这边axios 封装一旦配好前后端只要对着同一个接口定义联调就行。所以我一般建议哪怕是个人的小项目也先打开后端自动生成的文档看一眼再动手写前端页面。2.2 FastAPI 做后端的三个硬理由自动文档、Pydantic 校验、异步选 FastAPI 不是因为“新”而是它把前后端联调里最浪费时间的三个问题压到了最小。第一OpenAPI 自动文档。你只要写了路由函数和入参模型/docs页面自动给出可调试的接口列表。前端同事或者你自己不用翻 README 就能找到每个接口的入参、出参和错误码。第二Pydantic 做的请求体校验。客户端传进来的 JSON 在进业务逻辑之前已经被检查过了字段缺失、类型不对会直接返回 422不会等到业务代码里再踩空指针。第三async 支持。FastAPI 的路由可以声明成async def查询数据库等 IO 场景下并发能力明显优于同步框架。这里给一个最小路由示例你很快能感受到这个框架的“契约感”from fastapi import FastAPI from pydantic import BaseModel app FastAPI() class Item(BaseModel): name: str price: float app.post(/items) async def create_item(item: Item): return {item_id: 123, name: item.name, price: item.price}入参模型用的是 Pydantic 的BaseModel只要前端传的 JSON 里price不是数字、或者少了name字段接口直接返回校验错误不会进入执行体。post路由对应前端注册/创建类操作get对应查询类操作前后端对接时先对齐方法、路径、入参三个要素。2.3 Vue 端为什么配 Vite开发代理是联调的神器Vue 本身解决的是页面状态和视图的同步问题但真正让前后端分离开发“舒服”起来的是它的脚手架工具 Vite 提供的开发代理能力。开发阶段后端跑在http://localhost:8000前端跑在http://localhost:5173浏览器直接请求后端接口会触发跨域限制。Vite 代理的作用是你在前端代码里写的请求路径是/api/...Vite 开发服务器收到后转发到http://localhost:8000/api/...浏览器以为请求是同源的跨域问题在开发阶段就被绕开了。下面是一个典型的vite.config.js代理配置export default defineConfig({ server: { host: 0.0.0.0, port: 5173, proxy: { /api: { target: http://localhost:8000, changeOrigin: true } } } })proxy里的/api表示所有以/api开头的请求都转发给localhost:8000。changeOrigin: true会改写请求头里的Host字段让后端认为是浏览器直接访问的。这个配置的关键点在于前后端路径前缀必须一致我见过大量联调问题都出在“后端路径是/items前端代理写的是/api”这种前缀不匹配上。2.4 一个能长期维护的目录结构先看源码再动手拿到这类项目源码第一件事不是急着跑命令而是看目录结构。常见的可维护结构是“后端一个根目录、前端一个根目录”中间用 README 说明启动顺序。project-root/ ├── backend/ │ ├── app/ │ │ ├── main.py │ │ ├── routers/ │ │ ├── models/ │ │ └── schemas/ │ ├── requirements.txt │ └── .env ├── frontend/ │ ├── src/ │ │ ├── api/ │ │ ├── router/ │ │ └── views/ │ ├── vite.config.js │ └── package.json └── README.md我把电商类、内容管理类的小项目都按这个布局组织过。routers放接口路由models放数据库模型schemas放 Pydantic 模型前端api目录放 axios 实例和接口函数router放页面路由。照着源码跑通以后你真正学到的不是某一个页面怎么写而是这套固定结构怎么帮你省掉“接口函数散落各处”的维护成本。3. 最小运行骨架照着源码把 FastAPI 和 Vue 从零各起一个3.1 后端最小可运行文件三个文件跑通一个接口拿到项目源码后先把后端最小闭环跑起来。最常见的最小闭环是三个文件依赖清单、入口文件、环境变量。下面这份requirements.txt只放最必要的依赖运行源码前先安装这些fastapi uvicorn[standard] pydantic python-dotenv装依赖用下面这条命令建议在backend目录下创建一个虚拟环境再装避免污染全局 Pythoncd backend python -m venv .venv source .venv/bin/activate pip install -r requirements.txt逻辑说明虚拟环境把项目依赖和系统 Python 隔离开source .venv/bin/activate进入虚拟环境pip install -r按清单安装。参数说明uvicorn[standard]里的standard表示安装带uvloop、websockets等优化项的版本开发阶段用默认的uvicorn也可以差别主要体现在高并发和 WebSocket 场景。接下来是入口文件main.py源码里一般会从routers目录导入路由而不是把所有接口都堆在这个文件里from fastapi import FastAPI from app.routers import items app FastAPI(title前后端分离示例) app.include_router(items.router, prefix/api/v1) app.get(/health) async def health_check(): return {status: ok}启动命令是uvicorn app.main:app --reload --host 0.0.0.0 --port 8000app.main:app表示从app包里找main.py里的app实例--reload是开发模式下的热重载改代码自动重启。--host 0.0.0.0是为了让局域网里的其他设备也能访问比如你调试移动端时需要用手机访问电脑 IP。跑起来后打开http://localhost:8000/docs能看到自动生成的接口列表这就是联调时后端给你的“契约文档”。3.2 前端最小骨架Vite 创建项目并清理默认内容前端部分源码一般基于 Vue 3 Vite 搭建。创建项目的常见做法是从脚手架初始化然后把模板里的 HelloWorld 组件清掉替换成自己的路由和页面。cd frontend npm create vitelatest . -- --template vue npm install逻辑说明npm create vitelatest .里最后的点表示用当前目录创建项目--template vue选择 Vue 模板。装完依赖后src目录里默认会有App.vue、main.js、components等文件。清理步骤也固定把src/components里的示例组件删除App.vue只保留一个路由出口。这样做的原因是源码自带的演示代码往往会干扰你对项目结构的理解而且不清理的话后面你写自己的组件时很容易在模板默认样式里翻车。template router-view / /templaterouter-view是 Vue Router 的出口组件页面根据 URL 动态替换到这个地方。一个干净入口的好处是无论你的页面加多少路由配置都统一在router目录里管理不会出现组件间互相 import 导致循环依赖。3.3 数据模型的约定Pydantic 模型和数据库模型分开前后端分离项目里最容易写乱的就是数据模型。源码里你会发现schemasPydantic 模型和models数据库模型是两个目录这是必须养成的习惯。Pydantic 模型管“接口收到什么、返回什么”数据库模型管“数据怎么存”。举个例子创建商品时前端传name、price后端存库时还要自动加上created_at。如果这两个模型混在一起接口入参校验就会和数据库字段耦合字段一多就没法维护。典型 schema 定义长这样from pydantic import BaseModel, Field class ItemCreate(BaseModel): name: str Field(..., min_length1, max_length50) price: float Field(..., gt0)Field的第一个省略号表示该字段必填gt0表示价格必须大于 0。这些校验规则会在 OpenAPI 文档里自动体现前端同学看文档就知道要传什么格式的数据。这个“入参校验前置”的设计是 FastAPI 在前后端协作里最出彩的地方。3.4 一次请求的完整闭环从前端按钮到后端返回把前后端各自跑起来以后先用一个最简单的接口走通闭环再铺开业务代码。我的习惯是先不连数据库写一个返回固定 JSON 的接口确认整条链路通了再碰存储。// api 目录下的请求封装 import axios from axios const request axios.create({ baseURL: /api/v1, timeout: 10000 }) export function getItems() { return request.get(/items) }# routers/items.py from fastapi import APIRouter router APIRouter() router.get(/items) async def list_items(): return [{id: 1, name: 示例商品, price: 29.9}]前端baseURL用的是/api/v1而不是完整域名这正是开发代理的关键设计源码头里不带http://localhost:8000所有请求统一走/api前缀Vite 代理拿到后转发给后端。timeout参数设 10 秒超过时间自动报错避免接口无响应时页面一直转圈。后端list_items返回的字段名和结构就是前端页面要绑定的数据结构。还有一步很关键——用浏览器开发者工具验证curl http://localhost:8000/api/v1/items这个命令不走前端代理直接从网络层请求后端接口。如果这条能返回数据说明后端没问题如果前端页面还是拿不到数据问题就锁定在代理配置或 axios 封装上。排查范围的每一步都要这样收窄。4. 联调打通跨域、代理与第一个真实接口对接4.1 开发阶段为什么总报 CORS同源策略拆开看跨域报错是前后端分离项目的新手第一道坎。浏览器的同源策略要求“协议、域名、端口”三者一致才能发起请求开发阶段前端 5173、后端 8000端口不同天然不同源。所以你在浏览器控制台看到的CORS policy报错不是后端接口坏了而是浏览器拦截了响应。解决路径有两条后端开 CORS 中间件或者前端用 Vite 代理。两条都行但我建议开发阶段优先配代理因为生产环境部署时你大概率会让反向代理去处理这个问题开发和生产的行为保持一致踩的坑最少。4.2 后端开 CORS最快但不是最优后端开 CORS 是最直接的方案三行代码就能让前端跨端口访问from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins[http://localhost:5173], allow_methods[*], allow_headers[*] )参数作用建议值allow_origins允许跨域访问的来源开发环境写前端地址生产别用*allow_methods允许的 HTTP 方法需要PUT、DELETE时用[*]或显式列出allow_headers允许的请求头需要自定义头时按需添加这个方案的缺点是生产环境如果前后端域名不一致后端每个允许的域名都要写进配置还要考虑preflight预检请求的逻辑。所以我只在临时 Demo 里用它正式项目一律走代理或网关。4.3 Vite 代理配置开发环境的正确打开方式代理方案在前端目录的vite.config.js里配置核心是把/api前缀的请求转发到后端地址。export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8000, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ) } } } })rewrite这一步是关键它把/api前缀去掉再转发。要不要 rewrite取决于后端路由前缀。如果后端用include_router(..., prefix/api/v1)那么前端请求/api/v1/items代理应该转发给后端的/api/v1/items此时不能 rewrite。只有当前端路径里有多余前缀时才需要去掉。这个“要不要 rewrite”的问题几乎每个联调项目里都会有人踩一次。我的判断标准是先看后端路由完整路径再决定代理是否改写不要照抄网上的配置。4.4 axios 实例封装统一处理返回结构和错误项目源码里真正有复用价值的部分是api目录下的 axios 实例。封装的核心目的有两个所有请求统一加baseURL以及统一处理错误状态。import axios from axios import { ElMessage } from element-plus const request axios.create({ baseURL: /api/v1, timeout: 15000 }) request.interceptors.response.use( (response) response.data, (error) { const status error.response?.status if (status 401) { // 登录失效跳回登录页 window.location.href /login } else if (status 422) { ElMessage.error(参数校验失败请检查输入) } return Promise.reject(error) } ) export default request响应拦截器把response直接解包成data页面里拿到的就是后端返回的 JSON 本体不用每次res.data.data这样剥洋葱。422分支对应前面说的 Pydantic 校验失败后端返回的详情里会写明哪个字段错了前端直接提示用户不用再去猜“为什么没反应”。401分支在整个项目引入登录功能后非常省事所有接口自动获得“登录失效跳登录页”的能力。4.5 联调失败时先看三处日志、Network、代理路径联调出了问题从三个位置按顺序看后端终端日志、浏览器 Network 面板、代理的路径。我的经验是90% 的问题出在路径不匹配而不是 CORS。后端终端如果有请求进来说明代理转发是通的后端没日志说明请求根本没到问题在前端代理或 axios。Network 面板看请求的完整 URL 和状态码404是路径不对422是参数不对500才是后端代码错误。按这个顺序排查十分钟内基本能定位问题。我见过太多人在 CORS 上反复折腾最后发现是少写了一个s导致接口路径 404。5. 避坑源码实战里高频翻车点的现象、原因与解法5.1 刷新页面就 404历史路由模式下的经典问题现象从首页点击跳转到/about一切正常但直接访问http://localhost:5173/about或者刷新页面就显示 404。原因开发服务器默认处理不了 Vue Router 的history模式。history模式用的是 HTML5 History APIURL 变化不会真正向后端发请求但刷新时浏览器会按路径发起真实请求开发服务器找不到这个文件就回 404。解决两种方式一种是在 Vite 配置里加appType: spa让开发服务器回退到首页另一种是把路由改成createWebHashHistory()URL 变成/#/about刷新时不会触发请求。生产环境下反向代理配置try_files $uri $uri/ /index.html;也能解决。这个坑的本质是“前端路由是假的后端没有对应文件”理解这一点后就不会再被它卡住。5.2 Swagger 能打开、前端却跨域协议与来源问题现象直接浏览器访问http://localhost:8000/docs一切正常Swagger 里调接口也拿得到数据但前端页面一请求就报 CORS。原因docs页面是从后端域名访问的属于同源请求跨域限制不生效。前端页面从localhost:5173访问localhost:8000浏览器判断不同端口预检请求发出去后后端没有返回正确的 CORS 头于是拦截。解决检查allow_origins里是否确实写了http://localhost:5173注意端口不能丢。另一个隐蔽点如果前端页面是通过 IP 访问而不是 localhost来源会变成http://192.168.x.x:5173这个来源也要加进白名单。用代理方案则不存在这个问题因为浏览器看到的请求是同源的。5.3 Pydantic 校验一直报 422前端日期传成了字符串现象前端表单提交的日期字段在后端一直校验失败报datetimeexpected 之类前端看自己传的值又没什么问题。原因Vue 组件里日历控件的值通常是2024-06-01这样的字符串FastAPI 的 Pydantic 模型字段声明为datetime类型时默认校验收不了不带时区的纯日期字符串部分地区格式下还会遇到YYYY/MM/DD和YYYY-MM-DD的兼容问题。解决Pydantic 模型里用date类型而非datetime或者前端提交前把日期统一转成T00:00:00Z格式。前者更贴合业务语义后者兼容性更好。我给源码做适配时习惯先在/docs里手动传一次值确认后端接受什么格式再回前端改——不要靠猜。5.4 数据库返回的对象序列化失败Pydantic 与 ORM 混用现象后端查询数据后直接return user_object前端拿到的结果里有些字段是[object Object]或者直接报“对象不可序列化”。原因ORM 返回的是数据库模型实例不是普通字典。Pydantic 把任意对象序列化成 JSON 时有两种做法用from_attributesTrue让它可以读取 SQLAlchemy 模型的属性或者手动转成字典。没配置这两个时序列化会直接失败。解决给入参和出参 Pydantic 模型都加上model_config ConfigDict(from_attributesTrue)前后端分离项目的接口返回最好显式声明出参模型class ItemOut(BaseModel): id: int name: str price: float model_config ConfigDict(from_attributesTrue)这样做的另一个好处是接口文档会显示返回的数据结构前端可以拿文档当类型声明直接用。5.5 代理配置了但请求还是 502目标地址写错现象Vite 代理配好了前端发请求报 502 Bad Gateway后端日志又没有请求记录。原因target写错是最常见的情况。比如开发机 IP 变了没同步更新或者后端换过端口代理转发到了没有服务在听的地方。解决先手动访问一下target地址如果直接访问也打不开说明后端没起或地址不对如果能打开说明代理和目标之间还有网络隔离检查防火墙。把代理配置里的target改成和浏览器地址栏完全一致的地址用http://localhost:8000而不是localhost:8000后者在某些环境下会把协议解析成http://以外的类型。6. 进阶把 JWT 鉴权和构建部署一次串起来前后端分离的项目跑到能增删改查还只是骨架真正的完整链路要走到登录鉴权和上线部署。JWT 鉴权的常见做法是后端登录接口校验用户名密码后用jwt库签发 token前端拿到后存进 localStorageaxios 请求拦截器里统一加Authorization: Bearer token头后端用依赖注入校验 token 身份。这一步要注意 token 不会自动刷新过期后拦截器要做的不是弹错误而是跳登录页清掉本地 token。密码存储始终用哈希不要用明文。部署时FastAPI 服务和 Vue 构建产物有两种合并思路。第一种是前端构建后把dist目录挂到 FastAPI 的静态资源路径下后端一个进程同时服务 API 和页面适合个人项目。第二种是用反向代理API 请求转发给 FastAPI 的8000静态文件直接交给代理处理前端路由的 history 模式也要在这里配置回退规则。第二种是主流因为前端静态资源可以单独加缓存接口服务也可以独立扩容。验证整个系统是否健康的习惯动作是先用 curl 直接打后端接口确认数据正常再用浏览器无痕窗口访问前端页面走一遍完整流程最后看代理和后端的访问日志有没有 404 和 500。日志里一旦有规律性报错不要等用户发现。这个方向值不值得投入如果你手里的小项目还要加用户、加权限、加移动端那现在把前后端分离和 JWT 这套玩熟后面每一块都是在复用这套骨架。我的习惯是每接手一个新项目先跑通最小闭环再谈功能因为链路通了以后后面的效率是倍增的链路没通就堆功能最后肯定推倒重来。希望帮到你。本文还有配套的精品资源点击获取