File Uploads
On the Express stack, Efesto wires Multer
into the middleware chain so an endpoint can accept multipart/form-data. You opt in
per method with a boolean flag; Efesto handles the rest and exposes the file(s) on the
request.
The _<verb>Multer / _<verb>MultipleMulter flags work on both stacks. On Express,
Efesto wires Multer and the file arrives in req.file / req.files. On Bun/Elysia,
Efesto sets the TypeBox body schema to t.File() / t.Files() and the file arrives in
ctx.body (see below). You can also accept files natively on
Elysia with your own t.File() body schema.
Single file
Two things are needed: document the body as multipart/form-data, and set the
_<method>Multer flag to true. The uploaded file is then available as req.file:
import { BaseApiService, SwaggerOptions } from "efesto";
import type { Request, Response } from "express";
class Documents extends BaseApiService {
constructor() {
super(__filename);
}
_postSwagger: SwaggerOptions = {
operationId: "uploadDocument",
requestBody: {
content: {
"multipart/form-data": {
schema: {
type: "object",
properties: {
file: { type: "string", format: "binary" },
},
},
},
},
},
};
// enable Multer for POST (single file)
_postMulter: boolean = true;
_post(req: Request, res: Response) {
const file = req.file; // the uploaded file (in memory)
return res.json({ name: file?.originalname, size: file?.size });
}
}
export default Documents;
The field name Multer expects for a single upload is file.
Multiple files
Add _<method>MultipleMulter = true alongside the single flag, document the body as
an array of binaries, and read req.files:
class Documents extends BaseApiService {
constructor() { super(__filename); }
_postSwagger: SwaggerOptions = {
operationId: "uploadDocuments",
requestBody: {
content: {
"multipart/form-data": {
schema: {
type: "object",
properties: {
files: {
type: "array",
items: { type: "string", format: "binary" },
},
},
},
},
},
},
};
_postMulter: boolean = true;
_postMultipleMulter: boolean = true; // required for multiple files
_post(req: Request, res: Response) {
const files = req.files; // array of uploaded files
return res.json({ count: Array.isArray(files) ? files.length : 0 });
}
}
The field name for multiple uploads is files.
How it behaves
- Efesto uses Multer's memory storage by default, so files arrive as buffers
(
req.file.buffer/file.buffer), nothing is written to disk for you. - The Multer middleware runs first in the chain, before validation and your
handler, so
req.file/req.filesare populated by the time the method runs. - The flags are read by method name:
_postMulter,_putMulter, etc., matching the HTTP verb of the endpoint.
On the Bun/Elysia stack
The same flags apply to a class extending BaseApiService. Instead of wiring Multer,
Efesto sets the route's TypeBox body schema (t.File() for _<verb>Multer, t.Files()
for _<verb>MultipleMulter) when you don't declare one yourself; the file arrives in
ctx.body as a web File:
import { BaseApiService, type Context, SwaggerOptions } from "efesto/elysia";
export default class extends BaseApiService {
_putSwagger: SwaggerOptions = {
operationId: "uploadFile",
requestBody: {
content: {
"multipart/form-data": {
schema: { type: "object", properties: { file: "string::binary" } },
},
},
},
};
_putMulter = true; // body schema becomes t.Object({ file: t.File() })
async _put({ body }: Context) {
const file = (body as { file: File }).file;
return file?.name;
}
}
The field names match Express: file for a single upload, files for multiple.
The flags exist on post / patch / put only.
Validating and storing files
Efesto gets the bytes to your handler; what you do next is your code. Validate type and size, then persist where you want, all inside the method:
_post(req: Request, res: Response) {
const file = req.file;
if (!file) return res.status(400).json({ error: "No file" });
if (file.size > 5 * 1024 * 1024) return res.status(413).json({ error: "Too large" });
if (!file.mimetype.startsWith("image/")) return res.status(415).json({ error: "Images only" });
// store file.buffer wherever you like: disk, S3, GCS, a database...
const id = persist(file.buffer, file.originalname); // your storage logic
return res.json({ id });
}
Cloud storage (S3, GCS), image processing, compression, and virus scanning are not
Efesto features. They are ordinary code you run against file.buffer using the
library of your choice.