File uploads (multipart/form-data)
DaloyJS treats multipart/form-data as a first-class request shape. Two helpers — fileField() and multipartObject() — let you describe an upload contract once, get runtime validation (size caps, MIME allowlists, filename matchers), an end-to-end-typed handler, and a correct OpenAPI document with multipart/form-data media type and format: "binary" file fields.
DaloyJS does not buffer file bodies for you: the runtime FormData entry stays a File or Blob, so handlers can stream it (file.stream()) directly to S3, disk, or another upstream.
Quick start
import { z } from "zod";
import { App, fileField, multipartObject } from "@daloyjs/core";
const app = new App({
// Optional defense-in-depth caps applied to every multipart request.
multipart: { maxFileBytes: 5_000_000, maxFields: 32, maxFiles: 4 },
});
app.route({
method: "POST",
path: "/avatars",
operationId: "uploadAvatar",
request: {
body: multipartObject({
title: z.string().min(1),
file: fileField({
maxBytes: 1_000_000,
accept: ["image/png", "image/jpeg"],
}),
}),
},
responses: { 201: { description: "Created" } },
handler: async ({ body }) => {
// body.file is a File; body.title is a validated string.
await uploadToS3(body.file.stream(), body.file.type);
return { status: 201 as const, body: { ok: true } };
},
});fileField() options
maxBytes— reject files larger than this many bytes.accept— MIME allowlist. Each entry can be exact ("image/png") or a wildcard ("image/*"/"*/*").filename(name)— predicate for filename validation, useful for forcing extensions.optional— whentrue, acceptundefined/nullvalues without raising.format— OpenAPI hint, defaults to"binary".
App-level safety caps
The framework already enforces bodyLimitBytes on every request. For multipart bodies you can layer additional limits via AppOptions.multipart:
new App({
bodyLimitBytes: 10 * 1024 * 1024,
multipart: {
maxFileBytes: 5_000_000, // single-file cap
maxFields: 32, // total fields (file + non-file)
maxFiles: 4, // total file uploads
},
});These caps are evaluated as soon as the body is parsed, so a request that exceeds them is rejected with 413 Payload Too Large (size) or 400 Bad Request (counts) before the handler runs.
OpenAPI emission
When the request body is built from multipartObject(), the OpenAPI generator emits multipart/form-data as the request body media type. Each fileField becomes { type: "string", format: "binary" } with optional x-accept and x-max-bytes annotations so codegen tools and humans both see the constraints.
Validation errors
Field-level failures are returned as a standard 422 Unprocessable Content problem+json document with one entry per failing field — same shape as JSON body validation, so clients have a single error path to handle.