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

Next.js多图片+表单数据上传至MongoDB与Cloudinary/本地实现方案

问题:Next.js 表单提交与多文件上传实现

我在最新版Next.js中实现表单提交逻辑,表单包含普通输入项和支持多文件上传的图片输入框。需求如下:

  • 通过/api/addDetails接口提交数据
  • 将表单文本数据存储到MongoDB
  • 图片上传至Cloudinary或本地服务器,并生成每张图片的URL保存到数据库,方便后续GET请求获取完整数据

尝试过Cloudinary、multer、formidable等工具,但频繁遇到请求头未定义、关键依赖错误、coffee-script错误等问题。以下是我的前端表单代码,需要对应的后端处理代码,也可以修改前端代码来实现功能。

"use client";
import axios from "axios";
import React, { FormEvent, useRef } from "react";

const AddDetails = () => {
  const nameRef = useRef<HTMLInputElement>(null);
  const descriptionRef = useRef<HTMLInputElement>(null);
  const fileRef = useRef<HTMLInputElement>(null);

  const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    const nameInput = nameRef.current!;
    const descriptionInput = descriptionRef.current!;
    const fileInput = fileRef.current!;

    const formData = new FormData();
    Array.from(fileInput.files ?? []).forEach(file => {
      formData.append("files", file);
    });
    formData.append("name", nameInput.value);
    formData.append("description", descriptionInput.value);

    try {
      await axios.post("/api/addDetails", formData, {
        headers: {
          "Content-Type": "multipart/form-data",
        },
      });
      alert("提交成功");
      // 重置表单
      nameInput.value = "";
      descriptionInput.value = "";
      fileInput.value = "";
    } catch (error) {
      console.error("提交失败", error);
      alert("提交失败,请重试");
    }
  };

  return (
    <>
      <form onSubmit={handleSubmit} className="max-w-md mx-auto p-4">
        <div className="mb-4">
          <label className="block text-sm font-medium mb-1">图片上传</label>
          <input
            type="file"
            name="files"
            ref={fileRef}
            multiple
            accept="image/*"
            className="mt-1 p-2 border border-gray-300 rounded-md w-full"
          />
        </div>

        <div className="mb-4">
          <label htmlFor="name" className="block text-sm font-medium mb-1">
            名称
          </label>
          <input
            type="text"
            id="name"
            name="name"
            ref={nameRef}
            required
            className="mt-1 p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm"
          />
        </div>

        <div className="mb-4">
          <label htmlFor="description" className="block text-sm font-medium mb-1">
            描述
          </label>
          <input
            type="text"
            id="description"
            name="description"
            ref={descriptionRef}
            required
            className="mt-1 p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 block w-full shadow-sm"
          />
        </div>

        <button
          type="submit"
          className="px-4 py-2 rounded-md bg-violet-600 text-white hover:bg-violet-700"
        >
          提交
        </button>
      </form>
    </>
  );
};

export default AddDetails;

解决方案

通用前置准备

  1. 安装依赖:
npm install mongoose
  1. 创建MongoDB模型(models/Detail.ts):
import mongoose from "mongoose";

const detailSchema = new mongoose.Schema({
  name: { type: String, required: true },
  description: { type: String, required: true },
  imageUrls: { type: [String], required: true },
  createdAt: { type: Date, default: Date.now },
});

export default mongoose.models.Detail || mongoose.model("Detail", detailSchema);
  1. 配置环境变量(.env.local):
MONGODB_URI=你的MongoDB连接字符串

方案一:使用Cloudinary上传图片

1. 额外安装依赖

npm install cloudinary

2. 补充环境变量

CLOUDINARY_CLOUD_NAME=你的Cloudinary云名称
CLOUDINARY_API_KEY=你的Cloudinary API密钥
CLOUDINARY_API_SECRET=你的Cloudinary API密钥

3. API路由实现(app/api/addDetails/route.ts)

import { NextResponse } from "next/server";
import cloudinary from "cloudinary";
import mongoose from "mongoose";
import Detail from "@/models/Detail";
import { writeFileSync } from "fs";
import path from "path";

// 配置Cloudinary
cloudinary.v2.config({
  cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
});

// 连接MongoDB
const connectDB = async () => {
  if (mongoose.connections[0].readyState) return;
  await mongoose.connect(process.env.MONGODB_URI!);
};

export async function POST(request: Request) {
  await connectDB();

  try {
    const formData = await request.formData();
    const name = formData.get("name") as string;
    const description = formData.get("description") as string;
    const files = formData.getAll("files") as File[];

    if (!name || !description || files.length === 0) {
      return NextResponse.json(
        { error: "缺少必要字段" },
        { status: 400 }
      );
    }

    // 上传图片到Cloudinary
    const imageUrls: string[] = [];
    for (const file of files) {
      const buffer = Buffer.from(await file.arrayBuffer());
      const tempPath = path.join(process.cwd(), "temp", file.name);
      writeFileSync(tempPath, buffer);

      const result = await cloudinary.v2.uploader.upload(tempPath, {
        folder: "nextjs-form-uploads",
      });
      imageUrls.push(result.secure_url);
    }

    // 保存数据到MongoDB
    const newDetail = new Detail({
      name,
      description,
      imageUrls,
    });
    await newDetail.save();

    return NextResponse.json(
      { message: "数据提交成功", data: newDetail },
      { status: 201 }
    );
  } catch (error) {
    console.error("处理请求失败", error);
    return NextResponse.json(
      { error: "服务器内部错误" },
      { status: 500 }
    );
  }
}

方案二:本地服务器上传图片

1. API路由实现(app/api/addDetails/route.ts)

import { NextResponse } from "next/server";
import mongoose from "mongoose";
import Detail from "@/models/Detail";
import { writeFileSync, mkdirSync, existsSync } from "fs";
import path from "path";

// 连接MongoDB
const connectDB = async () => {
  if (mongoose.connections[0].readyState) return;
  await mongoose.connect(process.env.MONGODB_URI!);
};

export async function POST(request: Request) {
  await connectDB();

  try {
    const formData = await request.formData();
    const name = formData.get("name") as string;
    const description = formData.get("description") as string;
    const files = formData.getAll("files") as File[];

    if (!name || !description || files.length === 0) {
      return NextResponse.json(
        { error: "缺少必要字段" },
        { status: 400 }
      );
    }

    // 创建上传目录
    const uploadDir = path.join(process.cwd(), "public", "uploads");
    if (!existsSync(uploadDir)) {
      mkdirSync(uploadDir, { recursive: true });
    }

    // 保存图片到本地并生成URL
    const imageUrls: string[] = [];
    for (const file of files) {
      const buffer = Buffer.from(await file.arrayBuffer());
      const fileName = `${Date.now()}-${file.name}`;
      const filePath = path.join(uploadDir, fileName);
      writeFileSync(filePath, buffer);

      // 生成可访问的静态资源URL
      const imageUrl = `/uploads/${fileName}`;
      imageUrls.push(imageUrl);
    }

    // 保存数据到MongoDB
    const newDetail = new Detail({
      name,
      description,
      imageUrls,
    });
    await newDetail.save();

    return NextResponse.json(
      { message: "数据提交成功", data: newDetail },
      { status: 201 }
    );
  } catch (error) {
    console.error("处理请求失败", error);
    return NextResponse.json(
      { error: "服务器内部错误" },
      { status: 500 }
    );
  }
}

注意事项

  1. 确保Next.js版本为13+(App Router),若使用Pages Router,API路由写法需调整
  2. 安装依赖后重启开发服务器,避免缓存问题
  3. 生产环境建议使用Cloudinary等云存储,本地存储仅适合开发或小流量场景
  4. 可添加文件类型校验,限制仅上传图片格式

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 17:17:40