FormData
In order to make a controller method accept form data requests, you need to set isForm
option to true
in the validation library wrapper.
import { withZod } from 'vovk-zod';
import { z } from 'zod';
import { post, prefix } from 'vovk';
export default class UserController {
@post()
static createUser = withZod({
isForm: true,
body: z.object({
email: z.string().email(),
name: z.string().min(2).max(100),
}),
async handle(req) {
// ...
},
});
}
The RPC method body
type will be inferred as FormData
.
import { UserRPC } from 'vovk-client';
const formData = new FormData();
formData.append('email', 'user@example.com');
formData.append('name', 'John Doe');
await UserRPC.createUser({
body: formData,
});
Accessing form data
The form data can be accessed using built-in req.formData()
method.
//...
export default class UserController {
@post()
static createUser = withZod({
isForm: true,
body: z.object({ /* ... */}),
async handle(req) {
const formData = await req.formData(); // FormData instance
// ...
},
});
}
But also with req.vovk.form()
that serializes the form data into an object.
// ...
export default class UserController {
@post()
static createUser = withZod({
isForm: true,
body: z.object({
email: z.string().email(),
name: z.string().min(2).max(100),
}),
async handle(req) {
const form = await req.vovk.form(); // { email: 'user@example.com', name: 'John Doe' }
// ...
},
});
}
Using lists
In case if the form data expects to contain one or more values for the same key, it’s recommended to use a union schema of value type and an array of value type, because FormData
doesn’t distinguish between one and multiple values.
import { withZod } from 'vovk-zod';
import { z } from 'zod';
import { post, prefix } from 'vovk';
export default class UserController {
@post()
static createUser = withZod({
isForm: true,
body: z.object({
tags: z.union([z.array(z.string(), z.string())]),
}),
async handle(req) {
const form = await req.vovk.form(); // { tags: ['tag1', 'tag2'] } or { tags: 'tag1' }
},
});
}
One value:
import { UserRPC } from 'vovk-client';
const formData = new FormData();
formData.append('tags', 'tag1');
await UserRPC.createUser({
body: formData,
});
Multiple values:
import { UserRPC } from 'vovk-client';
const formData = new FormData();
formData.append('tags', 'tag1');
formData.append('tags', 'tag2');
await UserRPC.createUser({
body: formData,
});
The same rule applies to files:
import { withZod } from 'vovk-zod';
import { z } from 'zod';
import { post, prefix } from 'vovk';
export default class UserController {
@post()
static createUser = withZod({
isForm: true,
body: z.object({
files: z.union([z.array(z.file()), z.file()]),
}),
async handle(req) {
const form = await req.vovk.form(); // { files: [File, File] } or { files: File }
// ...
},
});
}
Client-side validation limitations
Note that vovk-ajv does not have built-in support for OpenAPI-compatible format: "binary"
, and the file size/type etc. aren’t validated on the client side.
Also note that vovk-dto/validateOnClient doesn’t validate body
if it’s an instance of FormData
at all.