multer vs react-dropzone vs fine-uploader vs dropzone
前端文件上传方案选型:客户端交互库与服务端中间件深度对比
multerreact-dropzonefine-uploaderdropzone类似的npm包:

前端文件上传方案选型:客户端交互库与服务端中间件深度对比

dropzone、fine-uploader 和 react-dropzone 是专注于浏览器端文件上传交互的 JavaScript 库,提供拖拽、预览、进度条等 UI 功能。其中 dropzone 是经典的框架无关库,react-dropzone 是专为 React 设计的 Hooks 组件,而 fine-uploader 曾是功能最强大的商业级方案(现已停止维护)。multer 则完全不同,它是 Node.js 服务端用于处理 multipart/form-data 的中间件,负责接收和存储客户端上传的文件。本对比旨在厘清客户端交互与服务端处理的区别,并帮助开发者根据技术栈(React vs 原生)和项目需求选择合适的前端库,同时明确服务端必须配合 multer 或类似工具使用。

npm下载趋势

3 年

GitHub Stars 排名

统计详情

npm包名称
下载量
Stars
大小
Issues
发布时间
License
multer30,929,08412,08355.2 kB17212 天前MIT
react-dropzone19,482,74011,022343 kB217 天前MIT
fine-uploader43,6788,134-1229 年前MIT
dropzone018,406849 kB1171 个月前MIT

前端文件上传方案深度解析:交互库与服务端中间件的协同

在 Web 开发中,文件上传看似简单,实则涉及前后端紧密协作。很多开发者容易混淆“前端交互库”和“后端处理中间件”的角色。本文将深入对比 dropzone、fine-uploader、react-dropzone(前端三剑客)以及 multer(后端核心),帮你理清架构边界,做出正确的技术选型。

🏗️ 核心定位:前端交互 vs 后端处理

首先要明确一个关键概念:dropzone、fine-uploader 和 react-dropzone 运行在浏览器中,负责让用户选择文件、显示预览、展示进度条;而 multer 运行在Node.js 服务器上,负责接收 HTTP 请求中的二进制流并保存文件。

如果你只引入了 react-dropzone 却忘了在服务端配置 multer,上传一定会失败,因为浏览器发了数据但服务器不知道如何解析。

前端库的角色

dropzone 是一个独立的 JavaScript 库,它直接操作 DOM 元素,自动绑定拖拽事件。

// dropzone: 初始化一个带有默认 UI 的上传区域
import Dropzone from "dropzone";

Dropzone.autoDiscover = false; // 关闭自动发现

const myDropzone = new Dropzone("#my-dropzone", {
  url: "/api/upload",
  maxFilesize: 2, // MB
  acceptedFiles: ".jpg,.png"
});

myDropzone.on("complete", function(file) {
  console.log(`上传完成:${file.name}`);
});

react-dropzone 不提供默认 UI,它通过 Hook 暴露状态和方法,让你用 JSX 构建界面。

// react-dropzone: 使用 Hook 构建自定义 UI
import { useDropzone } from 'react-dropzone';

function MyUploadComponent() {
  const { getRootProps, getInputProps, isDragActive } = useDropzone({
    onDrop: acceptedFiles => {
      console.log('选中的文件:', acceptedFiles);
      // 这里通常配合 axios/fetch 发送到后端
    }
  });

  return (
    <div {...getRootProps()} style={{ border: '2px dashed #ccc', padding: '20px' }}>
      <input {...getInputProps()} />
      {isDragActive ? (
        <p>把文件拖到这里...</p>
      ) : (
        <p>拖拽文件到此处,或点击选择</p>
      )}
    </div>
  );
}

fine-uploader (已废弃) 曾经提供极其复杂的 API 来处理分片和重试。

// fine-uploader: 旧式配置写法 (不再推荐)
var uploader = new qq.FileUploaderBasic({
  element: document.getElementById("uploader"),
  action: "/api/upload",
  validation: {
    allowedExtensions: ["jpeg", "jpg", "png"],
    sizeLimit: 2 * 1024 * 1024
  }
});

后端中间件的角色

multer 是 Express/Koa 的中间件,它拦截请求,解析文件流,并将其挂载到 req.file 或 req.files 对象上。

// multer: 在 Node.js 服务端配置
import express from 'express';
import multer from 'multer';
import path from 'path';

const app = express();

// 配置存储策略
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/');
  },
  filename: (req, file, cb) => {
    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
    cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
  }
});

const upload = multer({ 
  storage: storage,
  limits: { fileSize: 2 * 1024 * 1024 }, // 限制 2MB
  fileFilter: (req, file, cb) => {
    if (file.mimetype.startsWith('image/')) {
      cb(null, true);
    } else {
      cb(new Error('只允许上传图片'));
    }
  }
});

// 使用中间件
app.post('/api/upload', upload.single('file'), (req, res) => {
  // req.file 包含上传的文件信息
  res.json({ message: '上传成功', file: req.file });
});

🎨 UI 控制权:开箱即用 vs 完全定制

选择前端库时,最大的区别在于你对 UI 的控制程度。

dropzone 提供了完整的默认样式和布局。这对于后台管理系统或快速原型非常友好,但你往往需要编写大量的 CSS 来覆盖它的默认样式以匹配你的设计系统。

/* dropzone: 需要覆盖默认样式 */
.dropzone {
  border: 2px dashed #0087F7;
  border-radius: 5px;
  background: white;
}

/* 自定义预览图样式 */
.dropzone .dz-preview .dz-image {
  border-radius: 4px;
}

react-dropzone 是“无头”(Headless)的。它不渲染任何 HTML,只给你 getRootProps 和 getInputProps。这意味着你可以用 Tailwind CSS、Styled Components 或任何 CSS 方案从零构建界面,完全没有样式冲突的烦恼。

// react-dropzone: 完全自定义样式
<div {...getRootProps()} className="flex flex-col items-center justify-center p-10 border-2 border-dashed border-gray-300 rounded-lg hover:bg-gray-50 transition">
  <input {...getInputProps()} />
  <svg className="w-12 h-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
    <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
  </svg>
  <p className="mt-2 text-sm text-gray-600">拖拽文件到此处</p>
</div>

fine-uploader 曾经允许通过模板字符串定制 UI,但配置极其繁琐,且依赖特定的 DOM 结构,在现代前端框架中集成困难。

// fine-uploader: 复杂的模板配置 (已废弃)
var uploader = new qq.FineUploader({
  element: document.getElementById('fine-uploader'),
  template: '<div class="qq-uploader"><div class="qq-upload-drop-area"><span>拖拽文件</span></div></div>',
  // 需要手动管理很多 DOM 类名
});

⚡ 功能特性对比:分片、重试与预览

在处理大文件或网络不稳定场景时,高级功能至关重要。

1. 文件预览

dropzone 内置了图片预览功能,自动生成缩略图。

// dropzone: 自动预览
new Dropzone("#demo", {
  thumbnailWidth: 200,
  thumbnailHeight: 200,
  previewTemplate: document.querySelector('#preview-template').innerHTML
});

react-dropzone 需要你自己读取文件并生成预览 URL。

// react-dropzone: 手动实现预览
const { getRootProps, getInputProps } = useDropzone({
  onDrop: acceptedFiles => {
    const previews = acceptedFiles.map(file => Object.assign(file, {
      preview: URL.createObjectURL(file)
    }));
    setFiles(previews);
  }
});

// 渲染时记得释放内存
useEffect(() => () => files.forEach(file => URL.revokeObjectURL(file.preview)), [files]);

multer 不涉及预览,它只处理二进制流。预览必须在前端完成。

// multer: 无预览功能,仅保存文件
// 预览逻辑完全在前端浏览器中通过 URL.createObjectURL 实现

2. 分片上传与断点续传

这是 fine-uploader 曾经的杀手锏,但现在需要谨慎看待。

fine-uploader 原生支持分片上传和自动重试,无需额外代码。

// fine-uploader: 原生分片 (库已死,勿用)
chunking: {
  enabled: true,
  partSize: 2000000, // 2MB
  success: {
    endpoint: "/api/upload_success"
  }
}

dropzone 本身不支持分片上传。你需要引入额外的插件或自己编写逻辑将文件切片,然后循环发送。

// dropzone: 需手动实现分片 (伪代码)
const chunkSize = 1024 * 1024; // 1MB
for (let start = 0; start < file.size; start += chunkSize) {
  const chunk = file.slice(start, start + chunkSize);
  // 手动发送 chunk formData
  await uploadChunk(chunk, start, file.size);
}

react-dropzone 同样不内置分片,但因为在 React 生态中,你可以方便地结合 axios 或 fetch 编写自定义的分片上传 Hook,或者使用更上层的库如 uppy。

// react-dropzone: 结合自定义逻辑实现分片
const uploadInChunks = async (file) => {
  const totalChunks = Math.ceil(file.size / chunkSize);
  for (let i = 0; i < totalChunks; i++) {
    const start = i * chunkSize;
    const chunk = file.slice(start, start + chunkSize);
    await sendChunk(chunk, i, totalChunks, file.name);
  }
};

multer 默认一次性接收整个文件。如果要支持分片,需要在服务端编写逻辑合并文件块,或者使用 multer-s3 等插件直接流式传输到云存储。

// multer: 服务端合并分片逻辑 (需自定义)
app.post('/upload-chunk', upload.single('chunk'), (req, res) => {
  const { chunkIndex, totalChunks, filename } = req.body;
  // 逻辑:将 req.file 追加到临时文件,检查是否所有分片到位,若是则合并
});

⚠️ 维护状态与风险提示

这是本次对比中最重要的一点。

fine-uploader 已经停止维护。其 GitHub 仓库已归档,npm 包不再更新。在现代浏览器中使用可能存在兼容性风险,且发现安全漏洞时无人修复。绝对不要在新项目中使用它。

dropzone 维护状态良好,更新稳定,适合非 React 项目。但它是一个有状态的类库,在 React/Vue 等响应式框架中使用时,容易因为直接操作 DOM 而与框架的生命周期产生冲突,需要小心封装。

react-dropzone 由 React 社区活跃维护,完美遵循 React 最佳实践,无 DOM 操作冲突,是 React 项目的标准选择。

multer 是 Node.js 生态的事实标准,维护非常活跃。几乎所有基于 Express 的文件上传教程和生产环境都在使用它。

📊 选型决策矩阵

特性dropzonefine-uploaderreact-dropzonemulter
运行环境浏览器浏览器浏览器 (React)Node.js 服务端
UI 风格默认完整 UI默认完整 UI (旧式)无 UI (Headless)无 UI
React 集成困难 (需封装)极难完美 (Hooks)N/A (后端)
分片上传需手动/插件原生支持 (但库已死)需手动实现需自定义合并逻辑
维护状态✅ 活跃❌ 已停止✅ 活跃✅ 活跃
适用场景传统 jQuery/原生项目仅限遗留系统React/Next.js 项目Express/Koa 后端

💡 架构师建议

  1. React 项目:毫不犹豫选择 react-dropzone。它让你拥有完全的 UI 控制权,且没有技术债务。如果需要分片上传,可以配合 axios 自行实现,或引入 uppy (另一个现代上传库)。
  2. 非 React 项目:选择 dropzone。它能让你在几分钟内拥有一个功能完善的上传组件,覆盖 90% 的常规需求。
  3. 服务端必选:只要你是 Node.js (Express/Koa) 后端,multer 是必选项。它是连接前端上传请求和服务器文件系统的桥梁。
  4. 避坑指南:看到旧教程推荐 fine-uploader 时请直接忽略。它的分片功能虽然强大,但库的死亡意味着长期的安全隐患。现代网络环境和 CDN 服务通常提供了更好的分片上传解决方案,无需依赖一个过时的库。

总结:文件上传架构 = react-dropzone/dropzone (前端交互) + multer (后端接收)。根据前端框架二选一,后端统一用 multer,坚决避开 fine-uploader。

如何选择: multer vs react-dropzone vs fine-uploader vs dropzone

  • multer:

    当你的后端运行在 Node.js (Express 或 Koa) 环境时,必须选择 multer 来处理文件上传请求。它不是前端库,而是服务端中间件,负责解析 multipart/form-data 并将文件保存到磁盘或内存。任何前端上传方案(无论是 dropzone 还是 react-dropzone)都需要配合 multer 或类似的后端服务才能完成完整的上传流程。

  • react-dropzone:

    如果你的项目基于 React 构建,首选 react-dropzone。它不提供预设 UI,而是通过 Hooks (useDropzone) 和无头组件模式将拖拽逻辑注入到你的自定义组件中,完美契合 React 的声明式风格。这让你能完全控制样式和交互细节,轻松集成 Material UI 或 Tailwind CSS,避免处理非 React 库带来的 DOM 冲突。

  • fine-uploader:

    切勿在新项目中使用 fine-uploader。该库已正式停止维护,不再接收安全更新或功能修复。虽然它曾经支持分片上传和多种后端协议,但现有的现代替代方案(如 uppy 或原生实现)更安全且维护活跃。仅在维护极其陈旧的遗留系统时考虑暂时保留。

  • dropzone:

    如果你的项目使用原生 JavaScript、jQuery 或非 React 框架(如 Vue 2 通过包装器),且需要开箱即用的完整 UI(拖拽区、预览、进度条),选择 dropzone。它配置简单,社区成熟,适合快速集成到传统服务端渲染页面或老项目中,但需注意其默认样式可能需要覆盖。

multer的README

Multer NPM Version NPM Downloads Build Status Test Coverage OpenSSF Scorecard Badge

Multer is a node.js middleware for handling multipart/form-data, which is primarily used for uploading files. It is written on top of busboy for maximum efficiency.

NOTE: Multer will not process any form which is not multipart (multipart/form-data).

Installation

$ npm install multer

Usage

Multer adds a body object and a file or files object to the request object. The body object contains the values of the text fields of the form, the file or files object contains the files uploaded via the form.

Basic usage example:

Don't forget the enctype="multipart/form-data" in your form.

<form action="/profile" method="post" enctype="multipart/form-data">
  <input type="file" name="avatar" />
</form>
const express = require('express')
const multer  = require('multer')
const upload = multer({ dest: 'uploads/' })

const app = express()

app.post('/profile', upload.single('avatar'), function (req, res, next) {
  // req.file is the `avatar` file
  // req.body will hold the text fields, if there were any
})

app.post('/photos/upload', upload.array('photos', 12), function (req, res, next) {
  // req.files is array of `photos` files
  // req.body will contain the text fields, if there were any
})

const uploadMiddleware = upload.fields([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }])
app.post('/cool-profile', uploadMiddleware, function (req, res, next) {
  // req.files is an object (String -> Array) where fieldname is the key, and the value is array of files
  //
  // e.g.
  //  req.files['avatar'][0] -> File
  //  req.files['gallery'] -> Array
  //
  // req.body will contain the text fields, if there were any
})

In case you need to handle a text-only multipart form, you should use the .none() method:

const express = require('express')
const app = express()
const multer  = require('multer')
const upload = multer()

app.post('/profile', upload.none(), function (req, res, next) {
  // req.body contains the text fields
})

Here's an example on how multer is used in a HTML form. Take special note of the enctype="multipart/form-data" and name="uploaded_file" fields:

<form action="/stats" enctype="multipart/form-data" method="post">
  <div class="form-group">
    <input type="file" class="form-control-file" name="uploaded_file">
    <input type="text" class="form-control" placeholder="Number of speakers" name="nspeakers">
    <input type="submit" value="Get me the stats!" class="btn btn-default">
  </div>
</form>

Then in your javascript file you would add these lines to access both the file and the body. It is important that you use the name field value from the form in your upload function. This tells multer which field on the request it should look for the files in. If these fields aren't the same in the HTML form and on your server, your upload will fail:

const multer  = require('multer')
const upload = multer({ dest: './public/data/uploads/' })
app.post('/stats', upload.single('uploaded_file'), function (req, res) {
  // req.file is the name of your file in the form above, here 'uploaded_file'
  // req.body will hold the text fields, if there were any
  console.log(req.file, req.body)
});

Complete client-side and server-side examples (browser FormData uploads of a blob and of an image) are in the examples/ folder.

API

File information

Each file contains the following information:

KeyDescriptionNote
fieldnameField name specified in the form
originalnameName of the file on the user's computer, or the full path when preservePath: true
encodingEncoding type of the file
mimetypeMime type of the file
sizeSize of the file in bytes
destinationThe folder to which the file has been savedDiskStorage
filenameThe name of the file within the destinationDiskStorage
pathThe full path to the uploaded fileDiskStorage
bufferA Buffer of the entire fileMemoryStorage

multer(opts)

Multer accepts an options object, the most basic of which is the dest property, which tells Multer where to upload the files. In case you omit the options object, the files will be kept in memory and never written to disk.

By default, Multer will rename the files so as to avoid naming conflicts. The renaming function can be customized according to your needs.

The following are the options that can be passed to Multer.

KeyDescription
defCharsetDefault character set to use for text field values that do not declare one. Default: 'utf8'
defParamCharsetDefault character set to use for values of part header parameters (e.g. filename) that are not extended parameters (that contain an explicit charset). Default: 'latin1'
dest or storageWhere to store the files
fileFilterFunction to control which files are accepted
fileHwmhighWaterMark of each file stream (file.stream). Default: busboy's default
highWaterMarkhighWaterMark of the multipart parser stream. Default: busboy's default
limitsLimits of the uploaded data
preservePathKeep the full client-supplied path in file.originalname instead of just the base name
streamHandlerFunction that feeds the request body to busboy; defaults to req.pipe(busboy)

In an average web app, only dest might be required, and configured as shown in the following example.

const upload = multer({ dest: 'uploads/' })

When preservePath is enabled, Multer passes the incoming filename through with any path segments provided by the client. This is exposed as file.originalname; it does not change the destination folder, create directories, or sanitize the path for you. file.originalname is always client-supplied and should be treated as untrusted; with preservePath it additionally contains the path segments the client sent. Normalize or validate it before using it in a custom filename or storage engine.

If you want more control over your uploads, you'll want to use the storage option instead of dest. Multer ships with storage engines DiskStorage and MemoryStorage; More engines are available from third parties.

.single(fieldname)

Accept a single file with the name fieldname. The single file will be stored in req.file.

.array(fieldname[, maxCount])

Accept an array of files, all with the name fieldname. Optionally error out if more than maxCount files are uploaded. The array of files will be stored in req.files.

Files skipped by fileFilter do not count towards maxCount. Use limits.files to bound how many files are read from a request.

.fields(fields)

Accept a mix of files, specified by fields. An object with arrays of files will be stored in req.files.

fields should be an array of objects with name and optionally a maxCount. Example:

[
  { name: 'avatar', maxCount: 1 },
  { name: 'gallery', maxCount: 8 }
]

.none()

Accept only text fields. If any file upload is made, error with code LIMIT_UNEXPECTED_FILE will be issued.

.any()

Accepts all files that come over the wire. An array of files will be stored in req.files.

WARNING: Make sure that you always handle the files that a user uploads. Never add multer as a global middleware since a malicious user could upload files to a route that you didn't anticipate. Only use this function on routes where you are handling the uploaded files.

storage

DiskStorage

The disk storage engine gives you full control on storing files to disk.

const crypto = require('crypto')

const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, '/tmp/my-uploads')
  },
  filename: function (req, file, cb) {
    crypto.randomBytes(16, function (err, raw) {
      if (err) return cb(err)
      cb(null, file.fieldname + '-' + raw.toString('hex'))
    })
  }
})

const upload = multer({ storage: storage })

There are three options available: destination, filename and flush. All of them except flush are functions that determine where the file should be stored.

destination is used to determine within which folder the uploaded files should be stored. This can also be given as a string (e.g. '/tmp/uploads'). If no destination is given, the operating system's default directory for temporary files is used.

Note: You are responsible for creating the directory when providing destination as a function. When passing a string, multer will make sure that the directory is created for you.

filename is used to determine what the file should be named inside the folder. If no filename is given, each file will be given a random name that doesn't include any file extension.

Note: Multer will not append any file extension for you, your function should return a filename complete with a file extension.

Each function gets passed both the request (req) and some information about the file (file) to aid with the decision.

Note that req.body might not have been fully populated yet. It depends on the order that the client transmits fields and files to the server.

For understanding the calling convention used in the callback (needing to pass null as the first param), refer to Node.js error handling

flush is an optional boolean. When set to true, the file data is flushed to disk with an fsync call before the upload handler runs, which prevents data loss if the process or the system crashes while the data is still sitting in the operating system's page cache. It is disabled by default because forcing a disk flush on every upload has a performance cost. Note that this syncs the file contents only; if the new directory entry must survive a crash as well, fsync the destination directory yourself.

MemoryStorage

The memory storage engine stores the files in memory as Buffer objects. It doesn't have any options.

const storage = multer.memoryStorage()
const upload = multer({ storage: storage })

When using memory storage, the file info will contain a field called buffer that contains the entire file.

WARNING: Uploading very large files, or relatively small files in large numbers very quickly, can cause your application to run out of memory when memory storage is used.

limits

An object specifying the size limits of the following optional properties. Multer passes this object into busboy directly, and the details of the properties can be found on busboy's page.

limits can also be a function that receives the request and returns such an object. It is called once per request, before parsing starts, so limits can depend on e.g. the authenticated user:

const upload = multer({
  limits: function (req) {
    return { fileSize: req.user.maxUploadSize }
  }
})

Limits apply to the whole request; there is no per-file limit. To restrict individual files by type or name, use fileFilter or a custom storage engine.

The following integer values are available:

KeyDescriptionDefault
fieldNameSizeMax field name sizeInfinity
fieldSizeMax field value size (in bytes)1MB
fieldsMax number of non-file fieldsInfinity
fileSizeFor multipart forms, the max file size (in bytes)Infinity
filesFor multipart forms, the max number of file fieldsInfinity
partsFor multipart forms, the max number of parts (fields + files)Infinity
headerPairsFor multipart forms, the max number of header key=>value pairs to parse2000
fieldNestingDepthMax number of nesting levels for field names (e.g. a[b][c] has 2 levels)Infinity
fieldArrayIndexLimitMax numeric array index accepted inside a field name (e.g. a[3] uses index 3)Infinity

Specifying the limits can help protect your site against denial of service (DoS) attacks.

fileFilter

Set this to a function to control which files should be uploaded and which should be skipped. The function should look like this:

function fileFilter (req, file, cb) {

  // The function should call `cb` with a boolean
  // to indicate if the file should be accepted

  // To reject this file pass `false`, like so:
  cb(null, false)

  // To accept the file pass `true`, like so:
  cb(null, true)

  // You can always pass an error if something goes wrong:
  cb(new Error('I don\'t have a clue!'))

}

streamHandler

By default Multer reads the request with req.pipe(busboy). Some platforms consume the request body before your handlers run, so there is nothing left to pipe. Google Cloud Functions and Firebase Functions, for example, expose the already-read body as req.rawBody. Provide a streamHandler to feed busboy yourself in that case:

const upload = multer({
  storage: multer.memoryStorage(),
  streamHandler: function (req, busboy) {
    if (req.rawBody) {
      busboy.end(req.rawBody)
    } else {
      req.pipe(busboy)
    }
  }
})

The function receives the request and the busboy instance and must write the whole multipart body to busboy and end it.

Security

Specifying the limits can help protect your site against denial of service (DoS) attacks. The following limits are recommended for most applications:

  • fileSize -- set to the maximum expected file size for your use case
  • files -- set to the maximum number of files per request
  • fields -- set to the maximum number of text fields per request
  • fieldNestingDepth -- set to the minimum depth your field names require (e.g. 3 for a[b][c])
  • fieldArrayIndexLimit -- set to the largest array index your field names require (e.g. 100 for a[99])

Error handling

When encountering an error, Multer will delegate the error to Express. You can display a nice error page using the standard express way.

If you want to catch errors specifically from Multer, you can call the middleware function by yourself. Also, if you want to catch only the Multer errors, you can use the MulterError class that is attached to the multer object itself (e.g. err instanceof multer.MulterError).

Multer errors carry a code (e.g. LIMIT_FILE_SIZE) and, when the error concerns a specific field, its name in field. Errors about a specific file (LIMIT_FILE_SIZE, LIMIT_UNEXPECTED_FILE) also expose the client-supplied file name in filename; treat it as untrusted input, like file.originalname.

const multer = require('multer')
const upload = multer().single('avatar')

app.post('/profile', function (req, res) {
  upload(req, res, function (err) {
    if (err instanceof multer.MulterError) {
      // A Multer error occurred when uploading.
    } else if (err) {
      // An unknown error occurred when uploading.
    }

    // Everything went fine.
  })
})

Custom storage engine

For information on how to build your own storage engine, see Multer Storage Engine.

License

MIT