-
-
Save Webkadabra/7f25247c1e40fa218bd095db7d6700f7 to your computer and use it in GitHub Desktop.
Example express endpoint that resizes an image from B2 Cloud Storage
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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