CORE CONCEPTS

fileUpload() Middleware

fileUpload() provides Multer-style multipart handling on top of the Web Standard request.formData() API. If you've used Multer with Express, the API will feel immediately familiar.

Basic Usage

Single file upload
import { fileUpload } from '@rasenganjs/futon'; const upload = fileUpload({ limits: { fileSize: 5 * 1024 * 1024 } }); // 5 MB router.post('/avatar', upload.single('avatar'), async (ctx) => { const file = ctx.get('file'); // UploadedFile return json({ size: file.size, name: file.originalname }); });

The Uploader Methods

fileUpload(options) returns an Uploader with five methods, each producing a Middleware:

MethodAcceptsPopulates
.single(field)One file on fieldctx.state.file: UploadedFile
.array(field, maxCount?)Up to maxCount files on fieldctx.state.files: UploadedFile[]
.fields(specs)The declared fieldsctx.state.files: Record<string, UploadedFile[]>
.none()Text fields only — any file is rejected
.any()Every file, regardless of field namectx.state.files: UploadedFile[]
Multiple files on named fields
const upload = fileUpload(); router.post( '/gallery', upload.fields([ { name: 'cover', maxCount: 1 }, { name: 'photos', maxCount: 10 }, ]), async (ctx) => { const { cover, photos } = ctx.get('files'); return json({ cover: cover?.[0]?.originalname, count: photos?.length }); } );

Non-file fields in the same multipart body become ctx.body — just like Multer's req.body.

UploadOptions

OptionDescription
storageStorage engine — defaults to MemoryStorage (see Storage Engines)
limits.fileSizeMaximum size of a single file, in bytes
limits.filesMaximum number of files in the whole request
limits.fieldsMaximum number of non-file text fields
fileFilter(ctx, info) => boolean | Promise<boolean> — reject files before they reach storage
Rejecting non-image uploads
const upload = fileUpload({ fileFilter: (ctx, info) => info.mimetype.startsWith('image/'), });

Unlike Multer, a rejected file produces an explicit 400 FILE_FILTER_REJECTED response rather than silently skipping the file.

Error Responses

Every rejection returns JSON with an error code you can match on:

Example error response
{ "error": { "code": "LIMIT_FILE_SIZE", "message": "File \"photo.png\" exceeds the 5242880 byte limit." } }
CodeCause
LIMIT_FILE_SIZEA file exceeded limits.fileSize
LIMIT_FILE_COUNTToo many files in the request
LIMIT_FIELD_COUNTToo many non-file fields
LIMIT_UNEXPECTED_FILEA file arrived on a field the selection doesn't allow
FILE_FILTER_REJECTEDfileFilter returned false
MALFORMED_BODYThe multipart body couldn't be parsed

Validation Order and Rollback

Every part of the multipart body is validated before anything is stored — limits, unexpected fields, and fileFilter are all checked first. If a file fails to store partway through a multi-file batch, already-stored files are rolled back via the storage engine's removeFile().

Custom Middleware
Storage Engines