@medley/multipart
A Medley plugin for parsing multipart/form-data
request bodies, which are primarily used for uploading files. It is written on top of Busboy
and inspired by multer
.
Installation
npm install @medley/multipart
yarn add @medley/multipart
Usage
Registering the plugin adds a .multipart()
method to the app
. This method creates a preHandler
hook that will parse multipart/form-data
bodies.
The hook will add a body
and a files
property to the req
object if it completes successfully, where req.body
will contain the text values of the form and req.files
will contain the uploaded files.
If an error occurs, req.body
and req.files
will remain undefined
.
Example:
<form action="/profile" method="post" enctype="multipart/form-data">
<input type="text" name="firstName" />
<input type="file" name="profilePhoto" />
</form>
const medley = require('@medley/medley');
const app = medley();
app.register(require('@medley/multipart'));
app.post('/profile', [
app.multipart({
profilePhoto: {maxCount: 1},
}),
], (req, res, next) => {
req.files.profilePhoto
req.body.firstName
});
API
Plugin Options
Option | Type | Description | Default |
---|
preservePath | boolean | If paths in the multipart 'filename' field shall be preserved. | false |
limits | object | Various limits on incoming data. Valid properties are: | |
limits.fieldNameSize | integer | Max field name size (in bytes). | 100 |
limits.fieldSize | integer | Max field value size (in bytes). | 1 MiB |
limits.fields | integer | Max number of non-file fields. | Infinity |
limits.fileSize | integer | The max file size (in bytes). | Infinity |
limits.files | integer | The max number of file fields. | Infinity |
limits.parts | integer | The max number of parts (fields + files). | Infinity |
limits.headerPairs | integer | The max number of header key-value pairs to parse. | 2000 |
Specifying the limits can help protect your server against denial of service (DoS) attacks.
const medley = require('@medley/medley');
const app = medley();
app.register(require('@medley/multipart'), {
limits: {
fieldSize: 100 * 1024,
fileSize: 5 * 1024 * 1024,
files: 4,
},
});
app.multipart(expectedFiles[, options])
Option | Type | Description |
---|
expectedFiles | object or 'ANY_FILES' | Required. An object mapping file field names to the maximum number of files expected for the field. See details below. |
options | object | The same as the global plugin option. Will be merged with the global plugin options (while taking precedence over them). |
The options
parameter allows for route-specific control over upload limits.
app.multipart({
profilePhoto: {maxCount: 1},
}, {
limits: {
fields: 1,
fileSize: 8 * 1024 * 1024,
},
})
expectedFiles
Specifies the expected file fields and limits the number of files that can be received for those fields. If unexpected files are received, the upload will be cancelled with an error.
{
fieldName: { maxCount: integer, optional?: boolean} | integer
}
app.multipart({
someFile: {maxCount: 1},
multipleFiles: {maxCount: 6},
moreFiles: 5,
})
Expected files are required by default, so an error will be thrown if an expected file is not uploaded. Set optional: true
to allow the upload to complete without receiving any files for a particular field.
app.post('/upload', [
app.multipart({
requiredFile: {maxCount: 1},
optionalFiles: {maxCount: 5, optional: true},
}),
], (req, res, next) => {
req.files.requiredFile
req.files.optionalFiles
});
The maxCount
value determines the value type of the property in the req.files
object. If maxCount
is 1
, the value will be a file object. If maxCount > 1
, the value will be an array of file objects (even if only a single file is received).
app.post('/upload', [
app.multipart({
oneFile: {maxCount: 1},
multipleFiles: {maxCount: 5},
}),
], (req, res, next) => {
req.files.oneFile
req.files.multipleFiles
});
If expectedFiles
is the special string 'ANY_FILES'
, any files may be uploaded and file objects will always be stored in an array.
Note that the 'ANY_FILES'
option should not be used in a production environment.
multipart.discardFiles(files)
Exported directly by the module, this method safely discards all of the files in the req.files
object.
This is only needed when the multipart preHandler
hook completes successfully but the files won't be handled (e.g. if an error happens).
const multipart = require('@medley/multipart');
multipart.discardFiles(req.files);
multipart.MultipartError
The error constructor that this plugin uses to create errors when something goes wrong. It can be used to check if an error was generated by multipart
inside an error handler.
const {MultipartError} = require('@medley/multipart');
app.setErrorHandler((err, req, res) => {
if (err instanceof MultipartError) {
}
});
See the code for the properties attached to MultipartError
objects.
File Object
Property | Description |
---|
stream | fs.ReadStream of the file. |
fileName | The name of the file on the user's computer. An empty string ('' ) if no name was supplied by the client. |
mimeType | The MIME type of the file reported by the client using the Content-Type header. |
size | The size of the file in bytes. |
Note: The file stream
must be handled in some way (even discarded by doing stream.destroy()
) to ensure that the underlying file descriptor gets closed and the temporary file gets deleted.
app.post('/profile', [
app.multipart({
profilePhoto: {maxCount: 1},
}),
], (req, res, next) => {
const {profilePhoto} = req.files;
profilePhoto.stream
profilePhoto.fileName
profilePhoto.mimeType
profilePhoto.size
});
Error Handling
When encountering an error, multipart
will delegate the error to Medley. Note that if multipart
did not create the error, you may need to discard the uploaded files.
const multipart = require('@medley/multipart');
app.setErrorHandler((err, req, res) => {
if (err instanceof multipart.MultipartError) {
} else if (req.files !== undefined) {
multipart.discardFiles(req.files);
}
});