Last active
June 26, 2024 15:04
-
-
Save sina-byn/d99028438812718cb60236cc45357eec to your computer and use it in GitHub Desktop.
express image optimization middleware
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
// code for https://medium.com/@sina-byn/code-an-express-middleware-to-optimize-your-images-c9225308e4ba | |
const { createReadStream } = require('fs'); | |
const express = require('express'); | |
const fs = require('fs/promises'); | |
const sharp = require('sharp'); | |
const path = require('path'); | |
// * utils | |
const getContentType = fileName => { | |
const ext = path.extname(fileName).slice(1); | |
let contentType; | |
switch (ext) { | |
case 'jpg': | |
case 'jfif': | |
case 'jpeg': | |
contentType = 'image/jpeg'; | |
break; | |
case 'png': | |
contentType = 'image/png'; | |
break; | |
case 'webp': | |
contentType = 'image/webp'; | |
break; | |
case 'svg': | |
contentType = 'image/svg+xml'; | |
break; | |
default: | |
contentType = 'application/octet-stream'; | |
} | |
return contentType; | |
}; | |
const app = express(); | |
app.get('*', async (req, res, next) => { | |
const storagePath = path.join(__dirname, 'public'); | |
const fileName = req.params[0] ?? ''; | |
const filePath = path.join(storagePath, fileName); | |
try { | |
const stats = await fs.stat(filePath); | |
if (!stats.isFile()) return next(); | |
const contentType = getContentType(fileName); | |
res.setHeader('Content-Type', contentType); | |
if (['image/svg+xml', 'application/octet-stream'].includes(contentType)) { | |
const readStream = createReadStream(filePath); | |
readStream.pipe(res); | |
readStream.on('error', next); | |
return; | |
} | |
const image = sharp(filePath); | |
const metadata = await image.metadata(); | |
const aspectRatio = metadata.width / metadata.height; | |
const quality = Math.trunc(+(req.query.q ?? 100)); | |
let width = Math.trunc(+(req.query.w ?? 0)); | |
let height = Math.trunc(+(req.query.h ?? 0)); | |
if (width && !height) { | |
height = Math.round(width * (1 / aspectRatio)); | |
} else if (height && !width) { | |
width = Math.round(height * aspectRatio); | |
} else { | |
width = metadata.width; | |
height = metadata.height; | |
} | |
const stream = image | |
.resize({ width, height }) | |
.jpeg({ quality, progressive: true, force: false }) | |
.webp({ quality, progressive: true, force: false }) | |
.png({ quality, progressive: true, force: false }); | |
stream.pipe(res); | |
stream.on('error', next); | |
} catch (err) { | |
console.error(err); | |
res.status(404).send('File not found'); | |
} | |
}); | |
app.listen(8080, () => { | |
console.log('server running at http://localhost:8080'); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment