import { Hono } from "hono"; import { cors } from "hono/cors"; import { etag } from "hono/etag"; import { logger } from "hono/logger"; import { prettyJSON } from "hono/pretty-json"; import { html } from "hono/html"; import sharp from "sharp"; const homepage = html` Resize Images API

Resize images API

Try now

Docs

The structure of the API path is:

/api/imgresize/:width/:height/:url

Where width and height can be numbers in pixels or auto.

The URL should be URL encoded, which can be done in JS with encodeURIComponent. See MDN ref.

Example for https://memori.ai/logo.png:

https%3A%2F%2Fmemori.ai%2Flogo.png

Then call the API as, for example:

/api/imgresize/200/200/https%3A%2F%2Fmemori.ai%2Flogo.png

You can also specify a format using the querystring ?format= and indicating one of the following: avif, gif, heif, jpeg, jpg, jp2, pdf, png, svg, tiff, webp. Note: Experimental!

Another querystring parameter is ?fit= which can be one of the following: cover, contain, fill, inside, outside (Ref: sharp docs). Default is inside.

`; const app = new Hono(); app.use(prettyJSON()); app.use(etag(), logger()); app.use("/api/*", cors()); app.get("/", (c) => { return c.html(homepage); }); app.post("/api/preview", async (c) => { const body = await c.req.parseBody(); c.header("Cache-Control", "s-maxage=31536000, stale-while-revalidate"); const url = body["url"]; if (!url || typeof url !== "string") { c.status(400); return c.json({ error: "No URL provided" }); } const width = body["width"]; const height = body["height"]; const w = width && width !== "auto" ? Number(width) : 200; const h = height && height !== "auto" ? Number(height) : 200; return c.redirect(`/api/imgresize/${w}/${h}/${encodeURIComponent(url)}`); }); app.get("/api/imgresize/:width/:height/:url", async (c) => { const { width, height, url } = c.req.param(); const fmtParam = c.req.query("format"); const fitParam = c.req.query("fit"); const format = fmtParam && sharp.format[fmtParam as keyof typeof sharp.format] !== undefined ? sharp.format[fmtParam as keyof typeof sharp.format] : sharp.format.jpeg; const fit = fitParam && sharp.fit[fitParam as keyof typeof sharp.fit] !== undefined ? sharp.fit[fitParam as keyof typeof sharp.fit] : sharp.fit.inside; c.header("Cache-Control", "s-maxage=31536000, stale-while-revalidate"); c.header("Content-Type", `image/jpeg`); const decodedUrl = decodeURIComponent(url as string); const readStream = await fetch(decodedUrl, { cache: "no-cache" }); const input = Buffer.from(await readStream.arrayBuffer()); const w = width && width !== "auto" ? Number(width) : undefined; const h = height && height !== "auto" ? Number(height) : undefined; const outputBuffer = await sharp(input) // outputBuffer contains JPEG image data // no wider and no higher than w and h pixels // and no larger than the input image .resize({ width: w, height: h, fit: fit, withoutEnlargement: true, }) .toFormat(format) .toBuffer(); c.status(200); return c.body(outputBuffer); }); export default { port: Bun.env.PORT || 8787, fetch: app.fetch, };