File uploads
A request with Content-Type: multipart/form-data makes @body() resolve to { fields, files } instead of a plain object.
import { Route, Post, body } from '@green-tea/core';import type { MultipartBody } from '@green-tea/core';
@Route('/profile')class ProfileController { @Post('/avatar') upload(@body() form: MultipartBody) { const name = form.fields.name; // string | string[] const avatar = form.files.avatar; // UploadedFile | UploadedFile[] return { name, size: Array.isArray(avatar) ? undefined : avatar?.size }; }}UploadedFile is { filename, contentType, data: Buffer, size } — the whole file is buffered in memory, no temp files.
Repeated fields — bodyDuplicates
Section titled “Repeated fields — bodyDuplicates”By default, a repeated field name keeps the last value ('last', matching urlencoded). Set bodyDuplicates: 'array' on createApp to accumulate repeats into a string[] instead — this applies to both urlencoded and multipart text fields:
const app = createApp({ modules: [ApiModule], bodyDuplicates: 'array' });Files under a repeated field name always become an array (UploadedFile[]), regardless of bodyDuplicates.
Per-endpoint override
Section titled “Per-endpoint override”@Post('/upload', { duplicates: 'array' })upload(@body() form: MultipartBody) { /* ... */ }Precedence: route duplicates → app bodyDuplicates → 'last'.
Limits
Section titled “Limits”Uploaded files are held in memory, so they’re bounded by the same maxBodyBytes limit as any other request body (over the limit → 413). maxParts (default 1000) caps the number of multipart parts per request:
const app = createApp({ modules: [ApiModule], limits: { maxParts: 500 } });Both limits can be overridden per route — an upload endpoint can allow a larger body than the rest of the API:
@Post('/avatar', { maxBodyBytes: 5_000_000, maxParts: 20 })uploadAvatar(@body() form: MultipartBody) { /* ... */ }A route’s maxBodyBytes / maxParts fall back to the server-wide limits when unset. A malformed multipart body (bad boundary, missing headers, truncated part) → 400.