File uploads
Accept multipart uploads, validate with z.file(), save to disk, return a typed JSON response.
Prerequisites
--allow-read/--allow-writefor the upload directory- Client sends
multipart/form-data(not JSON)
Server
import { HttpServer, z } from "jsr:@webtools/expressapi";
await Deno.mkdir("./uploads", { recursive: true });
export const server = new HttpServer()
.post("/uploads", async (req, res) => {
const { file, title } = req.body;
const safeName = file.name.replace(/[^a-zA-Z0-9._-]/g, "_");
const path = `./uploads/${crypto.randomUUID()}-${safeName}`;
const bytes = new Uint8Array(await file.arrayBuffer());
await Deno.writeFile(path, bytes);
return res.status(201).json({
id: path,
name: file.name,
size: file.size,
type: file.type,
title,
});
}, [], {
body: z.object({
file: z.file()
.maxSize(5 * 1024 * 1024)
.type(["image/", "application/pdf"]),
title: z.string().min(1).max(100),
}),
})
.get("/uploads/:name", async (req, res) => {
return await res.sendFile(`./uploads/${req.params.name}`);
});
export type AppRouter = typeof server;
if (import.meta.main) server.listen(5050);Client (FormData)
const form = new FormData();
form.append("title", "Cover");
form.append("file", fileInput.files[0]);
const res = await fetch("http://localhost:5050/uploads", {
method: "POST",
body: form,
});
const json = await res.json();HttpClient is ideal for JSON APIs. For browser file picks, raw FormData + fetch is usually simpler; keep the server response typed via res.json() for other typed callers.