Reputation: 15
Is there any way to get resize image when needed for example. want to make is so that /image.jpg give me original image but image_200x200.jpg gives me resized image with 200px width and height. I want to make it so that image gets resized when rendered so i wouldn't have to store resized image. Thanks in advance
Upvotes: 0
Views: 640
Reputation: 25659
Yes, there is. But you must be aware that by doing so, the image will be resized every time it is requested. If you expect to have heavy traffic on your site, this is not a sustainable solution unless you actually store these resized images in a cache of some sort.
And even if you do cache them, be aware that without any URL-signing (ensuring that the URL was issued by your server), a malicious user might want to SPAM your server with requests where they use random sizes every time, to force a resize operation on your server and overwhelm it.
Now that this is out of the way, let's try to find a solution.
Express provides a way to serve static files, which we can keep for when we don't need resizing. If a file is not found at this location, the next routes will be checked against. So in the next one, we'll try to check if a resize is wanted, before trying to match other routes. We can target those by using a regex matching a specific pattern.
This example will match URLs ending with _{n}x{n}
and a "jpeg", "jpg" or "png" extension:
app.use(express.static(Path.join(__dirname, 'public'))); // If a file is found
app.use('/(*_\\d+x\\d+.(jpe?g|png))', resizingMiddleware); // If resizing is needed
// ... Other routes
A middleware is a function called by Express when a route is requested. In this case, we can declare it like this:
function resizingMiddleware(req, res, next) {
const data = parseResizingURI(req.baseUrl); // Extract data from the URI
if (!data) { return next(); } // Could not parse the URI
// Get full file path in public directory
const path = Path.join(__dirname, 'public', data.path);
resizeImage(path, data.width, data.height)
.then(buffer => {
// Success. Send the image
res.set('Content-type', mime.lookup(path)); // using 'mime-types' package
res.send(buffer);
})
.catch(next); // File not found or resizing failed
}
As you've seen above, I used a parseResizingURI
to get the original file name, along with the requested dimensions. Let's write this function:
function limitNumberToRange(num, min, max) {
return Math.min(Math.max(num, min), max);
}
function parseResizingURI(uri) {
// Attempt to extract some variables using Regex
const matches = uri.match(
/(?<path>.*\/)(?<name>[^\/]+)_(?<width>\d+)x(?<height>\d+)(?<extension>\.[a-z\d]+)$/i
);
if (matches) {
const { path, name, width, height, extension } = matches.groups;
return {
path: path + name + extension, // Original file path
width: limitNumberToRange(+width, 16, 2000), // Ensure the size is in a range
height: limitNumberToRange(+height, 16, 2000), // so people don't try 999999999
extension: extension
};
}
return false;
}
In the middleware, you may also see a resizeImage
function. Let's write it using sharp:
function resizeImage(path, width, height) {
return sharp(path).resize({
width,
height,
// Preserve aspect ratio, while ensuring dimensions are <= to those specified
fit: sharp.fit.inside,
}).toBuffer();
}
In the end, we get the code below:
// Don't forget to install all used packages:
// $ npm install --save mime-types express sharp
const Path = require('path');
const mime = require('mime-types')
const sharp = require('sharp');
const express = require('express');
const app = express();
// Existing files are sent through as-is
app.use(express.static(Path.join(__dirname, 'public')));
// Requests for resizing
app.use('/(*_\\d+x\\d+.(jpe?g|png))', resizingMiddleware);
// Other routes...
app.get('/', (req, res) => { res.send('Hello World!'); });
app.listen(3000);
function resizingMiddleware(req, res, next) {
const data = parseResizingURI(req.baseUrl); // Extract data from the URI
if (!data) { return next(); } // Could not parse the URI
// Get full file path in public directory
const path = Path.join(__dirname, 'public', data.path);
resizeImage(path, data.width, data.height)
.then(buffer => {
// Success. Send the image
res.set('Content-type', mime.lookup(path)); // using 'mime-types' package
res.send(buffer);
})
.catch(next); // File not found or resizing failed
}
function resizeImage(path, width, height) {
return sharp(path).resize({
width,
height,
// Preserve aspect ratio, while ensuring dimensions are <= to those specified
fit: sharp.fit.inside,
}).toBuffer();
}
function limitNumberToRange(num, min, max) {
return Math.min(Math.max(num, min), max);
}
function parseResizingURI(uri) {
// Attempt to extract some variables using Regex
const matches = uri.match(
/(?<path>.*\/)(?<name>[^\/]+)_(?<width>\d+)x(?<height>\d+)(?<extension>\.[a-z\d]+)$/i
);
if (matches) {
const { path, name, width, height, extension } = matches.groups;
return {
path: path + name + extension, // Original file path
width: limitNumberToRange(+width, 16, 2000), // Ensure the size is in a range
height: limitNumberToRange(+height, 16, 2000), // so people don't try 999999999
extension: extension
};
}
return false;
}
Upvotes: 2