DEV Community

Костя Третяк
Костя Третяк

Posted on • Edited on

@ts-stack/multer simplifies uploading files to a Node.js-based backend

This package is actually a fork of the well-known native package for ExpressJS multer v2.0.0-rc.4. It will be interesting primarily to those developers who prefer Promise-style programming instead of middleware. In addition, it is also important that this package is written in TypeScript, so the type support and context documentation in it is top notch.

Install

Make sure you have Node.js >= v20.0.6 installed. The package can be installed with the following command:

npm install @ts-stack/multer
Enter fullscreen mode Exit fullscreen mode

Usage

Multer returns an object with four properties: textFields, file, files and groups. The textFields object contains the values of the text fields of the form, the file, files or groups object contains the files (as Readable stream) uploaded via the form.

The following example uses ExpressJS only for simplicity. In fact, @ts-stack/multer does not return middleware, so it is less convenient for ExpressJS than the original module. Basic usage example:

import { Multer } from '@ts-stack/multer';
import express from 'express';
import { createWriteStream } from 'node:fs';

// Here `avatar`, `photos` and `gallery` - is the names of the field in the HTML form.
const multer = new Multer({ limits: { fileSize: '10MB' } });
const parseAvatar = multer.single('avatar');
const parsePhotos = multer.array('photos', 12);
const parseGroups = multer.groups([{ name: 'avatar', maxCount: 1 }, { name: 'gallery', maxCount: 8 }]);
const app = express();

app.post('/profile', async (req, res, next) => {
  const parsedForm = await parseAvatar(req, req.headers);
  // parsedForm.file is the `avatar` file
  // parsedForm.textFields will hold the text fields, if there were any
  const path = `uploaded-files/${parsedForm.file.originalName}`;
  const writableStream = createWriteStream(path);
  parsedForm.file.stream.pipe(writableStream);
  // ...
});

app.post('/photos/upload', async (req, res, next) => {
  const parsedForm = await parsePhotos(req, req.headers);
  // parsedForm.files is array of `photos` files
  // parsedForm.textFields will contain the text fields, if there were any
  const promises: Promise<void>[] = [];
  parsedForm.files.forEach((file) => {
    const promise = new Promise<void>((resolve, reject) => {
      const path = `uploaded-files/${file.originalName}`;
      const writableStream = createWriteStream(path);
      writableStream.on('error', reject);
      writableStream.on('finish', resolve);
      file.stream.pipe(writableStream);
    });
    promises.push(promise);
  });

  await Promise.all(promises);
  // ...
});

app.post('/cool-profile', async (req, res, next) => {
  const parsedForm = await parseGroups(req, req.headers);
  // parsedForm.groups is an object (String -> Array) where fieldname is the key, and the value is array of files
  //
  // e.g.
  //  parsedForm.groups['avatar'][0] -> File
  //  parsedForm.groups['gallery'] -> Array
  //
  // parsedForm.textFields will contain the text fields, if there were any
});
Enter fullscreen mode Exit fullscreen mode

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

import { Multer } from '@ts-stack/multer';
import express from 'express';

const parsetextFields = new Multer().textFields();
const app = express();

app.post('/profile', async (req, res, next) => {
  const parsedForm = await parsetextFields(req, req.headers);
  // parsedForm.textFields contains the text fields
});
Enter fullscreen mode Exit fullscreen mode

Error handling

This is a list of error codes:

const errorMessages = new Map<ErrorMessageCode, string>([
  ['CLIENT_ABORTED', 'Client aborted'],
  ['LIMIT_FILE_SIZE', 'File too large'],
  ['LIMIT_FILE_COUNT', 'Too many files'],
  ['LIMIT_FIELD_KEY', 'Field name too long'],
  ['LIMIT_FIELD_VALUE', 'Field value too long'],
  ['LIMIT_FIELD_COUNT', 'Too many fields'],
  ['LIMIT_UNEXPECTED_FILE', 'Unexpected file field'],
]);
Enter fullscreen mode Exit fullscreen mode

You can see these error codes in the MulterError#code property:

import { Multer, MulterError, ErrorMessageCode } from '@ts-stack/multer';

// ...
try {
  const multer = new Multer().single('avatar');
  const parsedForm = await multer(req, req.headers);
  // ...
} catch (err) {
  if (err instanceof MulterError) {
    err.code // This property is of type ErrorMessageCode.
    // ...
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)