Skip to content

Medley plugin for parsing 'multipart/form-data' bodies

License

Notifications You must be signed in to change notification settings

medleyjs/multipart

Repository files navigation

@medley/multipart

npm Version Build Status Coverage Status dependencies Status

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
npm install @medley/multipart

# yarn
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 // The uploaded file (see File Object below)
  req.body.firstName // The user's first name
});

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, // 100 KiB
    fileSize: 5 * 1024 * 1024, // 5 MiB
    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, // 8 MiB
  },
})

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, // 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.

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');

// ...

// Something went wrong and the files need to be discarded
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) {
    // This is a multipart error
  }
});

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 // ReadStream {}
  profilePhoto.fileName // 'me.jpg'
  profilePhoto.mimeType // 'image/jpeg'
  profilePhoto.size // 3483297
});

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) {
    // This is a multipart error
  } else if (req.files !== undefined) {
    multipart.discardFiles(req.files);
  }
});

About

Medley plugin for parsing 'multipart/form-data' bodies

Resources

License

Stars

Watchers

Forks

Packages

No packages published