Skip to main content

MultipartReader

MultipartReader parses a multipart/form-data request body and exposes each part as a typed field or file item. It streams the incoming body using busboy and writes uploaded files to the OS temp directory.

Package: @opra/http


Obtaining an instance

Do not construct MultipartReader directly. Obtain one from the operation context:

import { HttpContext, MultipartReader } from '@opra/http';

async upload(ctx: HttpContext) {
if (!ctx.isMultipart) return;
const reader = await ctx.getMultipartReader();
}

Methods

getAll()

getAll(): Promise<MultipartReader.Item[]>

Reads all remaining parts from the stream and returns them as an array. Waits until the entire request body has been consumed.

Use getAll() when the upload is small and bounded, or when you need all parts before starting any processing.

const parts = await reader.getAll();

for (const part of parts) {
if (part.kind === 'file') {
await store(part.storedPath, part.filename);
}
if (part.kind === 'field') {
console.log(part.field, part.value);
}
}

await reader.purge(); // delete temp files when done

getNext()

getNext(): Promise<MultipartReader.Item | undefined>

Reads and returns the next part from the stream. Returns undefined when all parts have been consumed.

Use getNext() for large uploads or when you want to start processing a part before the rest of the body has arrived.

let part: MultipartReader.Item | undefined;

while ((part = await reader.getNext())) {
if (part.kind === 'file') {
// File is already written to disk at part.storedPath.
// Start processing immediately — the stream continues in parallel.
await processFile(part.storedPath);
await part.delete(); // free disk space as soon as possible
}

if (part.kind === 'field') {
options[part.field] = part.value;
}
}
note

If a field declared as required in .MultipartContent() was never sent by the client, getNext() throws a BadRequestError after the last part is consumed and the stream closes.


cancel()

cancel(): void

Aborts stream processing. No further parts are emitted after cancel() is called. Useful when an early validation error makes reading the rest of the body unnecessary.

const part = await reader.getNext();
if (!isAllowed(part)) {
reader.cancel();
throw new ForbiddenError('Upload not permitted');
}

purge()

purge(): Promise<void>

Deletes all temporary files created during this request. By default, uploaded files are automatically deleted when the LocalFile object is garbage-collected (autoDelete: true). Call purge() to delete them immediately after you are done processing.

const parts = await reader.getAll();
try {
await processAll(parts);
} finally {
await reader.purge();
}

Properties

PropertyTypeDescription
itemsMultipartReader.Item[]All parts received so far (grows as the stream is consumed).
scopestring | undefinedValidation scope applied to field decoding.

Events

MultipartReader extends EventEmitter. Listen to events for reactive processing instead of polling with getNext().

EventPayloadEmitted when
'item'MultipartReader.ItemAny part (field or file) becomes available.
'field'MultipartReader.FieldA form field part becomes available.
'file'MultipartReader.FileA file part finishes writing to disk.
'error'ErrorThe underlying stream encounters an error.
reader.on('file', (file: MultipartReader.File) => {
console.log(`Received file: ${file.filename} (${file.size} bytes)`);
});

reader.on('field', (field: MultipartReader.Field) => {
console.log(`Received field: ${field.field} = ${field.value}`);
});

reader.resume(); // start the stream when using event mode

Interfaces

MultipartReader.Field

Represents a non-file form field. The value property is decoded and validated against the type declared in .Field() on the operation.

PropertyTypeDescription
kind'field'Discriminator.
fieldstringField name as declared in .Field(fieldName, ...).
valueanyDecoded field value. Type is determined by the schema declaration.
mimeTypestring | undefinedMIME type reported by the client.
encodingstring | undefinedTransfer encoding reported by the client.

MultipartReader.File

Represents an uploaded file. Extends LocalFile. The file is written to a temporary path on disk before this item is emitted.

PropertyTypeDescription
kind'file'Discriminator.
fieldstringField name as declared in .File(fieldName, ...).
storedPathstringAbsolute path to the temp file on disk.
filenamestringOriginal file name sent by the client.
typestring | undefinedMIME type reported by the client, e.g. 'image/png'.
encodingBufferEncoding | undefinedTransfer encoding of the file.
sizenumberFile size in bytes (reads fs.statSync).
autoDeletebooleanWhen true, the temp file is deleted automatically when the object is GC'd. Defaults to true.
MethodReturnsDescription
buffer()Promise<Buffer>Reads the temp file into memory as a Buffer.
text()Promise<string>Reads the temp file as a UTF-8 string.
delete()Promise<void>Deletes the temp file immediately.

MultipartReader.Options

Options are passed internally by the adapter based on the .MultipartContent() declaration. The following properties come from busboy's config and are set via the first argument to .MultipartContent():

OptionTypeDescription
tempDirectorystringDirectory where uploaded files are written. Defaults to os.tmpdir().
scopestringValidation scope for field decoding.
highWaterMarknumberStream high-water mark in bytes.
fileHwmnumberFile stream high-water mark in bytes.
defCharsetstringDefault character set for non-file fields.
defParamCharsetstringDefault character set for parameter values.
preservePathbooleanPreserve the full path in filename.
limits.fieldNameSizenumberMax field name size in bytes.
limits.fieldSizenumberMax field value size in bytes.
limits.fieldsnumberMax number of non-file fields.
limits.fileSizenumberMax file size in bytes. Maps to maxFileSize in .MultipartContent().
limits.filesnumberMax number of file fields. Maps to maxFiles in .MultipartContent().
limits.partsnumberMax number of parts (fields + files).
limits.headerPairsnumberMax number of header key-value pairs per part.