@TypedFormData
export namespace TypedFormData {
export function Body<Multer extends IMulterBase>(
factory: () => Multer | Promise<Multer>,
): ParameterDecorator;
export type IMulterBase = ExpressMulter.Multer | FastifyMulter.Multer;
}Parameter decorator for multipart/form-data request bodies — the content type browsers send when an HTML form contains a <input type="file">. Use it for any endpoint that accepts file uploads alongside other fields.
Replaces the NestJS combination of @UploadedFile() / @UploadedFiles() / FileFieldsInterceptor / @ApiConsumes("multipart/form-data") / @ApiBody({...}) with a single decorator that reads everything from the parameter’s TypeScript type.
Only @TypedFormData.Body() is supported by @nestia/sdk’s generators. Vanilla @UploadedFile() routes don’t appear in the generated SDK or Swagger document.
Basic usage
import { TypedFormData, TypedRoute } from "@nestia/core";
import { Controller } from "@nestjs/common";
import Multer from "multer";
interface IArticleCreate {
title: string;
body: string | null;
thumbnail?: File;
attachments: File[];
tags: string[];
}
@Controller("articles")
export class ArticlesController {
@TypedRoute.Post()
public async create(
@TypedFormData.Body(() => Multer()) input: IArticleCreate,
): Promise<{ id: string }> {
// input.thumbnail is a Blob | undefined
// input.attachments is Blob[]
// input.title / body / tags are validated strings / arrays
return { id: "..." };
}
}Multipart fields with a binary file (browser sends Content-Disposition: form-data; name="…"; filename="…") become File (a browser-compatible Blob subclass). Plain fields become their declared TypeScript type with the same coercion rules as @TypedQuery.
What gets parsed
| Declared type | Wire payload | You receive |
|---|---|---|
File | one file field | File |
File[] | repeated file fields | File[] |
File? | optional file | File or undefined |
string | plain text field | "..." |
number | plain text field | coerced to number (or 400) |
boolean | plain text field | coerced to boolean (or 400) |
string[] | repeated plain fields | string[] |
| Tagged types | as above + validation | validated value (or 400) |
Constraints via tags.* work the same as on JSON bodies — string & tags.MinLength<3>, number & tags.Maximum<100>, etc.
File handling
The File you receive is a Blob. To read it as a buffer:
import { TypedFormData, TypedRoute } from "@nestia/core";
@TypedRoute.Post()
public async create(
@TypedFormData.Body(() => Multer()) input: { upload: File },
): Promise<void> {
const buf = Buffer.from(await input.upload.arrayBuffer());
// upload to S3, save to disk, etc.
}input.upload.name is the filename, input.upload.type the MIME type, input.upload.size the byte length.
Multer configuration
The factory argument is a function that returns a configured Multer instance. You control storage, file-size limits, and filtering through Multer’s standard options:
import Multer from "multer";
@TypedRoute.Post()
public async upload(
@TypedFormData.Body(() => Multer({
storage: Multer.memoryStorage(),
limits: { fileSize: 10 * 1024 * 1024 }, // 10 MB
})) input: IUpload,
): Promise<void> { ... }On Fastify, use @fastify/multipart instead:
import FastifyMultipart from "@fastify/multipart";
@TypedRoute.Post()
public async upload(
@TypedFormData.Body(() => FastifyMultipart()) input: IUpload,
): Promise<void> { ... }Comparison with vanilla NestJS
// Vanilla NestJS — separate decorators for files vs fields, separate Swagger schema
@Post()
@ApiConsumes("multipart/form-data")
@UseInterceptors(
FileFieldsInterceptor([
{ name: "thumbnail", maxCount: 1 },
{ name: "attachments" },
]),
)
@ApiBody({
schema: {
type: "object",
properties: {
title: { type: "string" },
body: { type: "string", nullable: true },
thumbnail: { type: "string", format: "binary" },
attachments: { type: "array", items: { type: "string", format: "binary" } },
},
},
})
async create(
@Body() body: any,
@UploadedFiles() files: { thumbnail?: Express.Multer.File[]; attachments?: Express.Multer.File[] },
) { ... }Versus the Nestia version above — one decorator, no Swagger schema, no UseInterceptors, no array-of-array unwrapping. The TypeScript type is the contract.
See also
- TypedBody — JSON bodies (when no files are involved).
- TypedQuery.Body — form-urlencoded bodies (text fields only).
- SDK — how
@TypedFormDataroutes appear in the generated client.