Hastily: Fastly Image Optimization for Express
raw JSON →Hastily is an Express.js middleware designed to replicate the functionality of the Fastly Image Optimization API directly within your Node.js application. It enables on-the-fly image transformations and optimizations by parsing Fastly-compatible URL parameters and applying them to images served by an existing Express static server or image-serving middleware. This allows developers to integrate powerful image manipulation capabilities without relying on an external CDN service for basic optimizations. The package is currently at version 0.5.0, indicating pre-1.0 development where minor version bumps might introduce breaking changes. Its key differentiators include its drop-in compatibility with Express, leverage of the high-performance `sharp` library for image processing, and its focus on mimicking the Fastly API for familiar usage patterns.
Common errors
error Error: Cannot find module 'sharp' from '<your-project-path>' ↓
npm install sharp in your project's root directory. Ensure that sharp installs successfully without compilation errors specific to your system. error SyntaxError: Cannot use import statement outside a module ↓
"type": "module" to your package.json file to enable ESM for your project, or rename your script files to .mjs extension. Alternatively, refactor to use CommonJS const { imageopto } = require('hastily'); syntax if your project must remain CommonJS. error Images are not being transformed or optimized by hastily, even with valid URL parameters. ↓
.jpg, .png, .webp, .svg). Verify your static file server is setting correct Content-Type headers (e.g., image/jpeg). If your setup is custom, consider providing a custom filter function to imageopto(). Warnings
gotcha Hastily requires `sharp` as a peer dependency. You must install `sharp` separately in your project (`npm install sharp`). Failure to do so will result in runtime errors. Ensure `sharp` is compatible with your Node.js version and architecture, as it relies on native binaries. ↓
gotcha By default, `hastily.imageopto()` only processes requests that it determines to be uncompressed images (based on URL extension and `Content-Type` header). If your images are not being transformed, verify the request URL and response headers. ↓
breaking As `hastily` is currently in pre-1.0 development (v0.5.0), minor version updates may introduce breaking API changes without a major version increment. Always review release notes when updating. ↓
gotcha Image processing with `sharp` (and thus `hastily`) is CPU and memory intensive. Running `hastily` in a production environment without proper caching strategies (e.g., HTTP caching, CDN caching) or adequate server resources can lead to significant performance degradation or out-of-memory errors, especially under heavy load. ↓
Install
npm install hastily yarn add hastily pnpm add hastily Imports
- imageopto wrong
import hastily from 'hastily'; const imageopto = hastily.imageopto;correctimport { imageopto } from 'hastily' - hasSupportedExtension wrong
import hastily from 'hastily'; const hasSupportedExtension = hastily.hasSupportedExtension;correctimport { hasSupportedExtension } from 'hastily' - imageopto, express wrong
const express = require('express'); const { imageopto } = require('hastily');correctimport express from 'express'; import { imageopto } from 'hastily';
Quickstart
import express, { static as expressStatic } from 'express';
import { imageopto } from 'hastily';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { mkdirSync, writeFileSync, existsSync } from 'fs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const app = express();
const imageDir = join(__dirname, 'www', 'images');
// Ensure the dummy image directory and a sample SVG exist for demonstration
if (!existsSync(imageDir)) {
mkdirSync(imageDir, { recursive: true });
}
const dummySvgPath = join(imageDir, 'test.svg');
if (!existsSync(dummySvgPath)) {
writeFileSync(dummySvgPath, '<svg width="100" height="100"><circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" /></svg>');
}
// Register hastily middleware before the static file server
app.use('/images', imageopto(), expressStatic(imageDir));
app.get('/', (req, res) => {
res.send(`<h1>Hastily Demo</h1>
<p>Visit <a href="/images/test.svg">/images/test.svg</a> for original.</p>
<p>Visit <a href="/images/test.svg?width=50&format=jpeg">/images/test.svg?width=50&format=jpeg</a> to see optimization (50px wide JPEG).</p>
<p>Visit <a href="/images/test.svg?height=20&fit=cover">/images/test.svg?height=20&fit=cover</a> for another optimization.</p>
<p>Visit <a href="/images/test.svg?quality=50&format=webp">/images/test.svg?quality=50&format=webp</a> for WebP with lower quality.</p>
`);
});
const PORT = process.env.PORT ?? 8000;
app.listen(PORT, () => {
console.log(`Hastily demo server listening on http://localhost:${PORT}`);
console.log(`Serving images from: ${imageDir}`);
});