From a2e0ba405b5b7d769f7ff8f23789fa6ef028db40 Mon Sep 17 00:00:00 2001 From: jason Date: Sat, 7 Mar 2026 22:59:01 -0600 Subject: [PATCH] feat: add image transform route with Sharp resize and crop --- backend/src/routes/image.ts | 65 +++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 backend/src/routes/image.ts diff --git a/backend/src/routes/image.ts b/backend/src/routes/image.ts new file mode 100644 index 0000000..e54974a --- /dev/null +++ b/backend/src/routes/image.ts @@ -0,0 +1,65 @@ +import { Router } from "express"; +import multer from "multer"; +import sharp from "sharp"; +import { TransformRequest } from "../types/image"; + +const upload = multer({ storage: multer.memoryStorage() }); +const router = Router(); + +router.post( + "/transform", + upload.single("file"), + async (req, res): Promise => { + if (!req.file) { + res.status(400).json({ error: "No file uploaded" }); + return; + } + + const { + width, + height, + quality, + format = "png", + fit = "inside", + position = "center" + } = req.body as any; + + const numericWidth = width ? Number(width) : undefined; + const numericHeight = height ? Number(height) : undefined; + const numericQuality = quality ? Number(quality) : 80; + + try { + let image = sharp(req.file.buffer); + + image = image.resize({ + width: numericWidth, + height: numericHeight, + fit, // "inside" (no crop) or "cover" (crop to fill) + position, // where to crop when fit === "cover" + withoutEnlargement: true + }); + + if (format === "webp") { + image = image.webp({ quality: numericQuality }); + } else if (format === "jpeg") { + image = image.jpeg({ quality: numericQuality }); + } else { + image = image.png({ quality: numericQuality, compressionLevel: 9 }); + } + + const outputBuffer = await image.toBuffer(); + + res.setHeader("Content-Type", `image/${format}`); + res.setHeader( + "Content-Disposition", + `attachment; filename="output.${format}"` + ); + res.send(outputBuffer); + } catch (err) { + console.error(err); + res.status(500).json({ error: "Image processing failed" }); + } + } +); + +export default router; \ No newline at end of file