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

React+Node.js文件上传报ENOENT无对应文件或目录错误修复

附件上传ENOENT报错修复方案

问题复现

实现附件上传功能时,预期将客户端上传的附件存储到client端public/images文件夹,前后端初始实现代码如下:

前端代码

function AddAttachment() {

    const history = useHistory();

    const {userId, setUserId} = useContext(UserContext);

    const link = window.location.pathname;
    const split = link.split("/");
    const projectId = split[4];

    const [file, setFile] = useState();
      const [fileName, setFileName] = useState("");
 
      const saveFile = (e) => {
        setFile(e.target.files[0]);
        setFileName(e.target.files[0].name);
      };
 
      const uploadFile = async (e) => {

        e.preventDefault();

        const formData = new FormData();
        formData.append("file", file);
        formData.append("fileName", fileName);
        try {
          const res = await Axios.post(
            "http://localhost:3001/uplatt",
            formData,
            projectId,
          ).then((response) => {
            if(response){
                window.alert('You have successfully uploaded an image!');
                    history.push(`/${userId}/viewproject/${projectId}`);
            }
          });

        } catch (ex) {
          console.log(ex);
        }
      };

  return (
    <div className="d-flex justify-content-center">
            <Card style={{ width: '70%' }}>
            <input type="file" name="file" onChange={saveFile} />
            <button onClick={uploadFile}>Upload</button>
            </Card>
        </div>
  )
}

后端代码

app.use(express.static("./public"));

var storage = multer.diskStorage({
    destination: (req, file, callBack) => {
        callBack(null, './public/images/')
    },
    filename: (req,file,callBack) => {
        callBack(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname))
    }
})

var upload = multer({
    storage: storage
});

app.post("/uplatt", upload.single('file'), (req,res) => {

    if(!req.file){
        console.log("No file upload");
    }
    else{
        console.log(req.file.filename)
        var imgsrc = 'http://127.0.0.1:3000/images/' + req.file.filename;
        var projectId = req.body.projectId;
        var insertData = "INSERT INTO project_attachment(project_attachment_url, project_id) VALUES (?, ?)"
        db.query(insertData, [imgsrc, projectId], (err, result) => {
            if (err){
                throw err
            } 
            else{
                console.log("file uploaded")
                res.send(result);
            }
        })
    }
})

运行时抛出错误:
Error: ENOENT: no such file or directory, open 'C:\Users\User\myapp\server\public\images\file-1656055704764.pdf'
排查发现multer存储路径默认基于服务端server目录解析,将destination配置为./client/public后,路径被解析为C:\Users\User\myapp\server\client\public\images\file-1656055704764.pdf,仍然不符合预期。

核心原因

  • 相对路径解析偏差:Node.js中./开头的相对路径是相对于执行node命令的工作目录,不是相对于当前代码文件所在目录,直接写./public/images或./client/public很容易出现路径拼接错误。
  • 传参位置错误:Axios.post的第三个参数是请求配置项,不是请求传参位置,当前代码把projectId放在第三个参数位置,后端无法从req.body中获取到projectId值。
  • 目录不存在:multer不会自动创建目标存储目录,如果public/images目录不存在,会直接抛出ENOENT错误。

修复步骤

  1. 使用绝对路径定位存储目录
    用Node.js内置path模块拼接绝对路径,避免相对路径解析问题。假设你的项目结构为根目录下分client、server两个文件夹,后端入口文件在server目录下,修改multer存储配置如下:
    // 顶部引入fs和path模块,已引入可跳过
    const path = require('path');
    const fs = require('fs');
    
    // 拼接client端public/images的绝对路径:__dirname是当前后端文件所在的server目录,../回到项目根目录
    const uploadDir = path.join(__dirname, '../client/public/images');
    // 检查目录是否存在,不存在则递归创建
    if (!fs.existsSync(uploadDir)) {
      fs.mkdirSync(uploadDir, { recursive: true });
    }
    
    var storage = multer.diskStorage({
        destination: (req, file, callBack) => {
            callBack(null, uploadDir)
        },
        filename: (req,file,callBack) => {
            callBack(null, file.fieldname + '-' + Date.now() + path.extname(file.originalname))
        }
    })
    
  2. 修正前端传参逻辑
    把projectId添加到FormData中,删除Axios.post第三个位置多余的projectId参数:
    const uploadFile = async (e) => {
      e.preventDefault();
      const formData = new FormData();
      formData.append("file", file);
      formData.append("fileName", fileName);
      formData.append("projectId", projectId); // 把projectId加入FormData
      try {
        const res = await Axios.post(
          "http://localhost:3001/uplatt",
          formData
        ).then((response) => {
          if(response){
              window.alert('You have successfully uploaded an image!');
                  history.push(`/${userId}/viewproject/${projectId}`);
          }
        });
      } catch (ex) {
        console.log(ex);
      }
    };
    
  3. 静态资源访问校验
    本地开发时React会自动将public目录下的文件作为静态资源服务,存储到client/public/images下的文件可以直接通过http://localhost:3000/images/文件名访问,原有后端拼接imgsrc的逻辑不需要修改。

注意:本地开发时服务端可以直接访问同机器上的client目录,生产环境下不建议把上传文件存在前端项目目录中,应该存储到服务端可管理的静态资源目录或专门的文件存储服务中。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 03:03:24