Outline
export namespace TypedFormData {
export function Body<Multer extends IMulterBase>(
factory: () => Multer | Promise<Multer>
): ParameterDecorator;
export type IMulterBase = ExpressMulter.Multer | FastifyMulter.Multer;
}
Request body decorator of multipart/form-data
.
@TypedFormData.Body()
is a request body decorator function, for the multipart/form-data
content type. It is useful for file uploading with additional data, and automatically casts property type following its DTO definition, performing the type validation.
As you can see from the below code, @TypedFormData.Body()
function is much easier and type safer than @UploadFile()
of NestJS. Also, if you're considering the SDK library generation, only @TypedFormData.Body()
is supported. Therefore, I recommend you to utilize @TypedFormData.Body()
instead of the @UploadFile()
function.
Of course, as every features of nestia
does, you don't need to define any extra schema definition for the Swagger Documents generation. @nestia/sdk
and @TypedFormData.Body()
will do everything just by analyzing your TypeScript types and codes.
import { TypedFormData, TypedRoute } from "@nestia/core";
import { Controller } from "@nestjs/common";
import Multer from "multer";
@Controller("bbs/articles")
export class BbsArticlesController {
@TypedRoute.Post()
public async create(
@TypedFormData.Body(() => Multer()) input: IBbsArticleCreate,
): Promise<void> {
input;
}
}
export interface IBbsArticleCreate {
title: string;
body: string | null;
thumbnail?: File | undefined;
files: File[];
tags: string[];
}
How to use
import { TypedFormData, TypedRoute } from "@nestia/core";
import { Controller } from "@nestjs/common";
import Multer from "multer";
import { IBbsArticleCreate } from "./IBbsArticleCreate";
@Controller("bbs/articles")
export class BbsArticlesController {
@TypedRoute.Post()
public async create(
@TypedFormData.Body(() => Multer()) input: IBbsArticleCreate,
): Promise<void> {
input;
}
}
Just call @TypedFormData.Body()
function on the request body parameter, that's all.
Nestia
will analyze your type (IBbsArticleCreate
), and writes optimal code for the target type, in the compilation level. If you click the "Compiled JavaScript File" tab of above, you can see the optimal transformation and validation code.
Such optimization is called AOT (Ahead of Time) compilation, and it is the secret of @TypedFormData.Body
.
By the way, if you're using fastify
, you have to setup fastify-multer
and configure like below when composing the NestJS application. If you don't do it, @TypedFormData.Body()
will not work properly, and throw 500 internal server error when Blob
or File
type being utilized.
// main.ts
import { NestFactory } from "@nestjs/core";
import {
FastifyAdapter,
NestFastifyApplication
} from "@nestjs/platform-fastify";
import FastifyMulter from "fastify-multer";
export async function main() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
);
app.register(FastifyMulter.contentParser as any);
await app.listen(3000);
}
// BbsArticlesController.ts
import { TypedFormData, TypedRoute } from "@nestia/core";
import { Controller } from "@nestjs/common";
import FastifyMulter from "fastify-multer";
import { IBbsArticleCreate } from "./IBbsArticleCreate";
@Controller("bbs/articles")
export class BbsArticlesController {
@TypedRoute.Post()
public async create(
@TypedFormData.Body(() => FastifyMulter()) input: IBbsArticleCreate,
): Promise<void> {
input;
}
}
Special Tags
You can enhance validation logic, of @TypedFormData.Body()
, through comment tags.
You know what? @TypedFormData.Body()
utilizes typia.assert<T>()
(opens in a new tab) function for form data validation, and the typia.assert<T>()
(opens in a new tab) function supports additional type checking logics through comment tags. For reference, "Type Tag" means a intersection type with atomic type and special tag type of typia
like number & tags.Type<"uint32">
, and "Comment Tag" means a comment starting from @
symbol following @${name} ${value}
format.
With those type and comment tags, you can add additional validation logics. If you want to add a custom validation logic, you also can do it. Read below Guide Documents of typia (opens in a new tab), and see the example code. You may understand how to utilize such type and comment tags, in a few minutes.
import typia, { tags } from "typia";
export const checkSpecialTag = typia.createIs<SpecialTag>();
interface SpecialTag {
int32: number & tags.Type<"int32">;
range?: number & tags.ExclusiveMinimum<19> & tags.Maximum<100>;
minLength: string & tags.MinLength<3>;
pattern: string & tags.Pattern<"^[a-z]+$">;
date: null | (string & tags.Format<"date">);
ip: string & (tags.Format<"ipv4"> | tags.Format<"ipv6">);
uuids: Array<string & tags.Format<"uuid">> &
tags.MinItems<3> &
tags.MaxItems<100>;
}
Restriction
When using @TypedFormData.Body()
, you've to follow such restrction.
At first, type of @TypedFormData.Body()
must be a pure object type. It does not allow union type. Also, nullable and undefindable types are not allowed, either. Note that, query parameter type must be a sole object type without any extra definition.
At next, type of properties must be atomic, Blob
, File
or array of them. In the atomic type case, the atomic type allows both nullable and undefindable types. However, mixed union atomic type like string | number
or "1" | "2" | 3
are not allowed. Also, the array type does not allow both nullable and undefindable types, either.
boolean
number
bigint
string
Blob
File
export interface SomeFormDataDto {
//----
// ATOMIC OR FILE TYPES
//----
// ALLOWED
boolean: boolean;
number: number;
string: string;
bigint: bigint;
optional_number?: number;
nullable_string: string | null;
literal_union: "A" | "B" | "C" | "D";
blob: Blob;
file: File;
// NOT ALLOWED
mixed_union: string | number | boolean;
mixed_literal: "A" | "B" | 3;
//----
// ARRAY TYPES
//----
// ALLOWED
nullable_element_array: (string | null)[];
string_array: string[];
number_array: number[];
literal_union_array: ("A" | "B" | "C")[];
literal_tuple: ["A", "B", "C"];
blobs: Blob[];
files: File[];
// NOT ALLOWED
optional_element_array: (string | undefined)[];
optional_array: string[] | undefined;
nullable_array: string[] | null;
union_atomic_array: (string | number)[];
mixed_literal_array: ("A", "B", 3)[];
mixed_tuple: ["A", "B", 3];
}