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
.
# npm
npm install @medley/multipart
# yarn
yarn add @medley/multipart
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 // The uploaded file (see File Object below)
req.body.firstName // The user's first name
});
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, // 100 KiB
fileSize: 5 * 1024 * 1024, // 5 MiB
files: 4,
},
});
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, // 8 MiB
},
})
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, // maxCount shorthand
})
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 // Will always exist
req.files.optionalFiles // May be `undefined` or an array of files
});
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.
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');
// ...
// Something went wrong and the files need to be discarded
multipart.discardFiles(req.files);
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) {
// This is a multipart error
}
});
See the code for the properties attached to MultipartError
objects.
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 // ReadStream {}
profilePhoto.fileName // 'me.jpg'
profilePhoto.mimeType // 'image/jpeg'
profilePhoto.size // 3483297
});
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) {
// This is a multipart error
} else if (req.files !== undefined) {
multipart.discardFiles(req.files);
}
});