Hastily: Fastly Image Optimization for Express

raw JSON →
0.5.0 verified Thu Apr 23 auth: no javascript

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.

error Error: Cannot find module 'sharp' from '<your-project-path>'
cause `sharp` is a peer dependency of `hastily` and must be installed manually by the consumer.
fix
Run 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
cause You are attempting to use ES Module `import` syntax in a Node.js environment configured for CommonJS. `hastily` examples often use ESM.
fix
Add "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.
cause Hastily's default filter function may not be recognizing your images, or they are not being served with appropriate content types. This often happens if the `Content-Type` header is missing or incorrect, or the file extension is not recognized.
fix
Ensure image files have supported extensions (e.g., .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().
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.
fix Run `npm install sharp` in your project's root directory. For specific environments (e.g., M1 Macs, Alpine Linux), consult `sharp`'s documentation for installation caveats.
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.
fix Ensure the image files have supported extensions (e.g., `.jpg`, `.png`, `.webp`, `.svg`). Verify that your static file server is setting correct `Content-Type` headers for image responses. You might need to provide a custom `filter` function to `imageopto()` if your setup is non-standard.
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.
fix Consult the `hastily` GitHub repository and npm changelog for specific breaking changes in new minor versions and adjust your code accordingly. Consider pinning to exact minor versions until 1.0 release.
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.
fix Implement robust HTTP caching headers (`Cache-Control`, `ETag`, `Last-Modified`) for optimized images. Consider using a reverse proxy or CDN to cache common image variations. Monitor server resource usage and scale accordingly.
npm install hastily
yarn add hastily
pnpm add hastily

This quickstart sets up an Express server with `hastily` to serve and optimize images from a local `www/images` directory, demonstrating basic usage of the `imageopto` middleware with Fastly-compatible URL parameters. It includes setup for a dummy image file for immediate testing.

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}`);
});