Skip to content

Instantly share code, notes, and snippets.

@IsaiahByDayah
Last active March 1, 2024 07:56
Show Gist options
  • Save IsaiahByDayah/3c3e07532a966e3188e457f9fd957ea8 to your computer and use it in GitHub Desktop.
Save IsaiahByDayah/3c3e07532a966e3188e457f9fd957ea8 to your computer and use it in GitHub Desktop.
Example express endpoint that resizes an image from B2 Cloud Storage
import * as admin from "firebase-admin"
import express from "express"
import sharp, { ResizeOptions, OutputOptions } from "sharp"
import fetch from "cross-fetch"
import FileType from "file-type"
import { parseQueryNumber, parseQueryString } from "../some/extra/util/functions"
import { Photo } from "../some/custom/types"
const router = express.Router()
const DEFAULT_QUALITY = 80
const DEFAULT_FORMAT = "jpg"
//REF: https://maxbarry.medium.com/dynamic-on-demand-image-resizing-using-firebase-hosting-and-google-cloud-functions-to-make-a-cheap-d64e8f5805d1
router.get("/:path", async (req, res) => {
// From the URL we want to get our passed parameters
const { query, params } = req
// The image path
const { path } = params
console.log(`Trying to fetch image at path ${path}`)
// Parse these params to integers
let width = parseQueryNumber("w", query) ?? parseQueryNumber("width", query)
let height = parseQueryNumber("h", query) ?? parseQueryNumber("height", query)
const quality =
parseQueryNumber("q", query) ??
parseQueryNumber("quality", query) ??
DEFAULT_QUALITY
let format =
(
parseQueryString("f", query) ?? parseQueryString("format", query)
)?.toLowerCase() ?? DEFAULT_FORMAT
// If you don't have a filepath then return a 404
if (!path?.length) {
console.log("No path supplied")
res.sendStatus(400)
return
}
const imageUrl = `{BASE B2 Url}/${path}`
console.log("Going to fetch image from: ", imageUrl)
// We're going to use Sharp.js to resize our image.
// Use the URL parameters to build options for Sharp
let resizeOpts: ResizeOptions = { width, height }
const formatOpts: OutputOptions = {
quality: Math.max(1, Math.min(100, quality)),
}
// We're going to use streams to do the following:
// read from our source image > pipe to Sharp > pipe to the HTTP response
// Read the remote file into that pipeline
let imageFetch: Response
try {
imageFetch = await fetch(imageUrl)
} catch (e) {
throw new Error("error with fetch call")
}
let imageArrayBuffer: ArrayBuffer
try {
imageArrayBuffer = await imageFetch.arrayBuffer()
} catch (e) {
throw new Error("error with convert to ArrayBuffer")
}
const imageBuffer = Buffer.from(imageArrayBuffer)
// Let's create a Sharp pipeline
let pipeline = sharp(imageBuffer)
// Now run the Sharp pipeline and pipe the output to the response
pipeline = pipeline.resize(resizeOpts)
// Reduce quality
pipeline = pipeline.toFormat(format, formatOpts)
// Write our content type response header
const fileType = await FileType.fromBuffer(await pipeline.toBuffer())
res.contentType(fileType.mime)
// If you wanted to store the resized image in B2 you could do it here...
// Return the resized image to the client
pipeline.pipe(res)
})
export default router
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment