Dynamic Image Resizing and Gallery Server

by Kuligaposten 2026-01-18

In this guide, we’ll walk through a Node.js Express server that handles image resizing, caching, and album management. This script provides a backend for photo galleries with optimized image delivery, caching, and album pagination.

1. Overview

This server provides:

  • A dynamic image resizing API that fetches images from remote HTTPS URLs, resizes them with sharp, caches the results, and serves them in WebP format.
  • An album management system that reads local directories under public/albums and serves album metadata and images via a RESTful API.
  • Pagination support for album images.
  • Built-in CORS support and static file serving for front-end integration.

2. Key Dependencies

The script uses the following libraries:

  • express – Web server framework.
  • cors – Enable Cross-Origin Resource Sharing.
  • fs & fs/promises – File system operations.
  • path – Path utilities.
  • sharp – High-performance image processing.
  • crypto – For caching keys.
  • flickrRoutes – A custom module (not included here) for Flickr integration.

3. Configuration

const IMAGE_CACHE_DIR = path.join(process.cwd(), "cache", "images");
const MAX_WIDTH = 2560;
const DEFAULT_QUALITY = 80;
const FETCH_TIMEOUT = 15000;
  • IMAGE_CACHE_DIR: Directory where resized images are cached.
  • MAX_WIDTH: Maximum width to resize images.
  • DEFAULT_QUALITY: Default WebP compression quality.
  • FETCH_TIMEOUT: Timeout for fetching remote images.

The cache directory is created if it doesn’t exist:

if (!fs.existsSync(IMAGE_CACHE_DIR)) {
  fs.mkdirSync(IMAGE_CACHE_DIR, { recursive: true });
}

4. Utility Functions

Two key utility functions:

function isValidHttpsUrl(url) {
  try {
    return new URL(url).protocol === "https:";
  } catch {
    return false;
  }
}

function imageCacheKey(url, width, quality, dpr) {
  return crypto
    .createHash("sha1")
    .update(`${url}|${width}|${quality}|${dpr}`)
    .digest("hex");
}
  • isValidHttpsUrl: Ensures only HTTPS images are allowed.
  • imageCacheKey: Generates a unique hash for caching resized images based on URL, width, quality, and device pixel ratio (DPR).

5. Dynamic Image Resizing Endpoint

Endpoint: GET /resize

Query parameters:

  • url – HTTPS image URL.
  • width – Desired width.
  • quality – WebP compression quality.
  • dpr – Device Pixel Ratio multiplier.

Workflow:

  1. Validate URL and parameters.
  2. Check if the image exists in cache.
  3. If not cached, fetch the image with timeout support.
  4. Validate response content type.
  5. Resize using sharp and convert to WebP.
  6. Save to cache and return the image.
res.set({
  "Content-Type": "image/webp",
  "Cache-Control": "public, max-age=31536000, immutable",
  "Access-Control-Allow-Origin": "*",
});

This ensures long-term caching and CORS-friendly delivery.


6. Albums API

The server dynamically reads albums stored in public/albums.

6.1 List All Albums

Endpoint: GET /api/albums

  • Returns metadata for all albums, including cover image, count, and URL.
  • Supports absolute URLs with the absolute=1 query parameter.
  • Covers are automatically picked from thumbnails (thumb) if available.

6.2 Album Images with Pagination

Endpoint: GET /api/albums/:album

  • Supports page and limit query parameters for pagination.
  • Includes image metadata (width, height, thumbnail URL, last modified date).
  • Sorts images by modification date.
  • Automatically matches thumbnails to full-size images.

Example response:

{
  "images": [
    {
      "src": "/albums/vacation/photo1.jpg",
      "thumb": "/albums/vacation/photo1-thumb.jpg",
      "alt": "Gallery: vacation",
      "date": "March 9, 2026",
      "bigWidth": 2048,
      "bigHeight": 1365,
      "thumbWidth": 500,
      "thumbHeight": 400
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 100,
    "totalPages": 5,
    "hasNextPage": true,
    "hasPrevPage": false
  }
}

7. Error Handling & 404

  • Returns meaningful HTTP status codes for invalid URLs, missing albums, or server errors.
  • A catch-all 404 endpoint:
app.use((req, res) => {
  res.status(404).json({ error: "Not found" });
});

8. Starting the Server

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});

The server listens on port 3023 by default and logs a confirmation message.


9. Benefits of This Approach

  • Optimized delivery: WebP conversion and resizing reduce bandwidth.
  • Caching: Avoids repeated processing for the same image.
  • Flexible albums: Easily extendable for local galleries or integrations like Flickr.
  • Pagination-ready: Efficient for large galleries.

10. Next Steps

  • Add authentication for private galleries.
  • Integrate CDN support for caching remote images.
  • Enhance error reporting for better monitoring.
  • Add image transformations like cropping, filters, or watermarking.

This script forms a solid foundation for a high-performance image gallery backend that can serve both web and mobile clients efficiently.

Back to Home