Upload
Harpia provides a robust Upload class to handle multipart form data. It validates and saves files to the filesystem, so your route handler can focus on the business logic — like processing or forwarding the file to cloud storage.
Configuration
You can configure the upload directory, field name, allowed extensions, maximum size, and an optional filename prefix.
import { Upload } from "@harpiats/core";
const uploader = new Upload({
path: "tmp/uploads", // Temporary directory before sending to cloud
fieldName: "avatar", // The name attribute of your HTML input
prefix: "avatar", // A prefix added to the saved file name
options: {
maxSize: 5 * 1024 * 1024, // 5 MB limit
allowedExtensions: [".jpg", ".png", ".jpeg", ".webp"],
allowedTypes: ["image/jpeg", "image/png", "image/webp"]
}
});
Handling Uploads
The Upload class provides two middleware methods: single and multiple.
Single File Upload
uploader.single acts as a middleware that validates and saves the file before your controller runs. If validation fails, it responds automatically with a 400 or 404. On success, your handler receives control with the file already on disk.
After the middleware runs, you can read the file back from the tmp path, process it, and send it to a cloud storage provider like Cloudflare R2 or Amazon S3.
import path from "node:path";
import fs from "node:fs/promises";
app.post("/profile/avatar", uploader.single, async (req, res) => {
// Access other text fields from the same multipart form
const formData = await req.formData();
const userId = formData.get("user_id") as string;
// Access the raw file object to get its name and build the local path
const file = formData.get("avatar") as File;
const localPath = path.join(process.cwd(), "tmp/uploads", `avatar-${file.name}`);
// Read the file bytes from disk
const fileBuffer = await fs.readFile(localPath);
// Upload to your cloud storage (example using S3-compatible SDK)
await s3Client.putObject({
Bucket: "my-bucket",
Key: `avatars/${userId}/${file.name}`,
Body: fileBuffer,
ContentType: file.type,
});
// Clean up the temporary file after a successful upload
await fs.unlink(localPath);
res.json({ message: "Avatar updated successfully!" });
});
Multiple File Uploads
To handle an array of files under the same field name, use uploader.multiple.
const attachmentsUploader = new Upload({
path: "tmp/attachments",
fieldName: "files",
options: {
maxSize: 10 * 1024 * 1024 // 10 MB per file
}
});
app.post("/messages/attachments", attachmentsUploader.multiple, async (req, res) => {
const formData = await req.formData();
const files = formData.getAll("files") as File[];
for (const file of files) {
const localPath = path.join(process.cwd(), "tmp/attachments", file.name);
const fileBuffer = await fs.readFile(localPath);
await s3Client.putObject({
Bucket: "my-bucket",
Key: `attachments/${file.name}`,
Body: fileBuffer,
ContentType: file.type,
});
await fs.unlink(localPath);
}
res.json({ message: "All attachments uploaded successfully!" });
});
If any file in a multiple upload fails validation, Harpia rejects the entire request, deletes any files already written to disk during that request, and returns a 400 Bad Request with an array describing each error.
Memory Warning
The maxRequestBodySize in your Server configuration still applies. Ensure your server limit accommodates the combined size of all files being uploaded.