You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Fetch API发表单数据时FastAPI返回422错误的排查求助

问题:Fetch API发送FormData到FastAPI时出现422/400错误

使用JavaScript的Fetch API发送FormData到FastAPI后端时,点击提交后收到Error 422: Unprocessable entity;手动添加Content-Type: multipart/form-data请求头则出现Error 400: Bad Request。

前端原代码

function register() {
  var formData = new FormData();
  var textInputName = document.getElementById('textInputName');
  var sexButtonActive = document.querySelector('#buttonsMW > .btn.active');
  var imagesInput = document.getElementById('imagesInput');

  formData.append('name', textInputName.value);
  if (sexButtonActive != null){
    formData.append('sex', sexButtonActive.html())
  } else {
    formData.append('sex', "");
  }
  formData.append('images', imagesInput.files[0]);

  fetch('/user/register', {
    method: 'POST',
    data: formData,
  })
  .then(response => response.json());
}
document.querySelector("form").addEventListener("submit", register);

后端原代码

@app.post("/user/register", status_code=201)
def register_user(name: str = Form(...), sex: str = Form(...), images: List[UploadFile] = Form(...)):
    try:
        print(name)
        print(sex)
        print(images)
        return "OK"
    except Exception as err:
        print(err)
        print(traceback.format_exc())
        return "Error"

错误原因分析

  • Fetch参数误用:Fetch API发送请求体的参数是body,不是data,原代码的data: formData会导致后端接收不到表单数据,触发422错误。
  • Content-Type手动设置错误:发送FormData时,浏览器会自动生成带boundary的multipart/form-data请求头(如multipart/form-data; boundary=----WebKitFormBoundaryxxx),手动添加不带boundary的请求头会让后端无法解析表单边界,引发400错误。
  • DOM元素取值错误:sexButtonActive.html()是jQuery方法,原生JS需用textContent或innerHTML获取内容,原代码此处会抛出错误,导致sex字段无法正确设置。
  • 文件接收类型不匹配:前端仅添加单个文件,但后端用List[UploadFile]接收多文件,类型不匹配触发422错误。

修复方案

1. 前端代码修复

function register(e) {
  // 阻止表单默认提交,避免页面刷新中断请求
  e.preventDefault();
  
  var formData = new FormData();
  var textInputName = document.getElementById('textInputName');
  var sexButtonActive = document.querySelector('#buttonsMW > .btn.active');
  var imagesInput = document.getElementById('imagesInput');

  formData.append('name', textInputName.value);
  if (sexButtonActive != null){
    // 原生JS用textContent获取文本内容
    formData.append('sex', sexButtonActive.textContent.trim())
  } else {
    formData.append('sex', "");
  }
  
  // 单文件场景:添加第一个文件;多文件则循环append所有文件
  if (imagesInput.files.length > 0) {
    formData.append('images', imagesInput.files[0]);
  }

  fetch('/user/register', {
    method: 'POST',
    // 用body代替data传递请求体
    body: formData
    // 无需手动设置Content-Type,浏览器自动处理
  })
  .then(response => response.json())
  .catch(err => console.error('请求错误:', err));
}
document.querySelector("form").addEventListener("submit", register);

2. 后端代码修复

根据文件接收需求调整:

  • 单文件接收:将List[UploadFile]改为UploadFile
  • 多文件接收:前端循环append所有文件(for (let file of imagesInput.files) { formData.append('images', file); }),后端保持List[UploadFile]

修复后的后端代码(单文件场景):

from fastapi import FastAPI, Form, UploadFile
import traceback

app = FastAPI()

@app.post("/user/register", status_code=201)
def register_user(name: str = Form(...), sex: str = Form(...), images: UploadFile = Form(...)):
    try:
        print(name)
        print(sex)
        print(images.filename)
        return {"status": "OK"}
    except Exception as err:
        print(err)
        print(traceback.format_exc())
        return {"status": "Error"}

内容的提问来源于stack exchange,提问作者Egor Richman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 07:20:30