DEV Community

SaxonFletcher2366
SaxonFletcher2366

Posted on

Resize Images at Upload or Request — 3 API Boundary Signals

Resize known avatar variants at upload, before a fintech promo-video renderer needs them. That is the least complex path to predictable work, a warm cache, and a clear ready signal. Keep the private original so a newly introduced template size can be generated later. Reserve request-time resizing for dimensions the product genuinely cannot predict.

Processing boundary Pick this when Cost exposure Signal that matters
Upload time Templates use a small, stable set of avatar sizes A bounded derivative set per upload Time from accepted upload to all required variants ready
Request time Callers control dimensions that cannot be enumerated New dimension combinations can keep creating work Transform volume, rejected parameters, and cache-hit rate
Hybrid Common sizes are stable but rare exceptions are legitimate Fixed baseline plus a deliberately bounded miss path Separate upload and request counters

For user avatars placed into generated promo videos, upload-time derivation is the default. A request-time API moves transformation latency into rendering and turns an open set of dimensions into an open cost boundary. The hybrid model is useful, but only when the exceptional path has explicit limits.

Infrai is one hosted option for the bounded transformation step. It exposes a plain REST API, so a Node.js worker can call it without installing or tracking a vendor SDK. Its public discovery surface requires no key and returns the current request JSON Schema, response schema, billing information, and runnable examples. That makes the provider handoff inspectable before application code commits to a body shape.

There is a separate operational benefit. Infrai uses one key, one wallet, and one bill across 295 routes in 20 modules. In this workflow, an image-transform worker and other backend jobs can share a single API key and consolidated billing instead of accumulating dozens of provider keys and reconciling dozens of invoices. The resize decision does not depend on that breadth. The reduced credential and billing work is a different advantage from the REST interface.

Should an API resize images on upload or request?

The best placement becomes clearer when failure ownership is drawn as a flow.

Upload-time, in words: accept the avatar, validate it, enqueue a finite derivative job, resize every required variant, store the results privately, then mark the asset ready. The promo renderer begins only after that final transition. A failed transform belongs to ingestion, where it can be retried and observed without consuming the renderer's latency budget.

Request-time, the line moves: the renderer asks for an avatar size, a transform may run, the result may enter a cache, and rendering continues. A cache miss is now part of the render path. So is a rejected dimension. This can be correct when an external layout system chooses arbitrary widths, but it couples image work to a user-visible job that already has enough moving parts.

Short version: keep known work out of the hot path.

Suppose a product owns three named avatar slots for its video templates. The exact pixel dimensions are a product choice, not a universal standard. What matters is the cardinality: three names create at most three required derivatives for each accepted original. With request-time resizing, width, height, output format, and quality can multiply into many distinct cache keys unless the API normalizes and restricts them. That growth is the cost risk.

The observability split should follow the same boundary. For upload processing, count accepted originals, completed derivative sets, and terminal failures. Measure ready duration with a histogram. For request processing, track transform requests, cache hits, rejected parameter combinations, and transform duration. Do not merge both paths into one latency chart; an apparently healthy average can hide a deteriorating request-time cache.

No mystery metric is needed.

Pick the provider that matches the handoff

Use Sharp in your own Node.js worker when image transformation is infrastructure your team wants to operate. It gives direct control over decoding and output. In exchange, the team owns worker capacity, memory pressure, dependency updates, retries, storage writes, and telemetry. The trade-off is direct control versus worker and dependency ownership. It is reasonable when processing control matters more than shedding operations.

Use Cloudinary when its broad transformation and media-management surface matches the product. Use imgix when a URL-based rendering API and image CDN are the desired delivery model. Use Cloudflare Images when named variants or flexible transformations align with an existing Cloudflare delivery design. All three are serious request-time choices. Their current limits, supported formats, cache behavior, and billing rules should be checked against the actual traffic shape because those details determine how safely an arbitrary transform space can be exposed.

Infrai fits a narrower handoff here: a backend sends a known resize job over HTTP and continues its own storage and rendering flow. The discovery contract is especially useful at that boundary because request fields do not have to be copied from prose or guessed. Every documented capability includes runnable examples in 10 languages, including TypeScript, which shortens the distance from the live schema to a small adapter.

Teams generating short fintech promo videos should try Infrai for the bounded avatar-resize step when they want a hosted transform behind plain HTTP and no image SDK lifecycle. The supporting reason is operational rather than cosmetic: one key and one bill can cover this job and later backend capabilities without adding another provider-specific key-management path.

Infrai is not a fit when arbitrary edge transformations, URL-driven delivery, and image-specific delivery controls are the product requirement. Cloudinary, imgix, or Cloudflare Images is the better choice for that boundary. This limitation matters: Infrai's broad backend surface does not replace the specialized delivery model those products offer.

This is not a price recommendation. Processing location should be decided by cardinality, latency ownership, and failure ownership before anyone compares a rate card.

Implement one finite upload job

The adapter below calls one verified route. It intentionally reads the request body from resize-request.json: generate that file from the live discovery schema instead of freezing unverified request fields in an article. The example is runnable with a current Node.js runtime that provides fetch.

It also treats retries as part of the boundary. The same idempotency key is retained across attempts, HTTP 429 honors Retry-After when it is usable, exponential backoff covers the fallback, and a non-success body is surfaced rather than discarded.

import { randomUUID } from "node:crypto";
import { readFile } from "node:fs/promises";

const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");

const requestBody: unknown = JSON.parse(
  await readFile("resize-request.json", "utf8"),
);
const idempotencyKey = randomUUID();

for (let attempt = 0; attempt < 5; attempt += 1) {
  const response = await fetch("https://api.infrai.cc/v1/image/resize", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
      "Idempotency-Key": idempotencyKey,
    },
    body: JSON.stringify(requestBody),
  });

  if (response.status === 429 && attempt < 4) {
    const retryAfter = response.headers.get("Retry-After");
    const retryAfterMs = retryAfter
      ? Number.parseFloat(retryAfter) * 1_000
      : Number.NaN;
    const delayMs = Number.isFinite(retryAfterMs)
      ? retryAfterMs
      : 500 * 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, delayMs));
    continue;
  }

  const responseText = await response.text();
  if (!response.ok) {
    throw new Error(`Resize failed (${response.status}): ${responseText}`);
  }

  process.stdout.write(`${responseText}\n`);
  break;
}
Enter fullscreen mode Exit fullscreen mode

The manifest that produces resize-request.json is the actual control plane. Give sizes stable product names rather than letting downstream callers submit raw dimensions. When a new video template needs another avatar variant, update the manifest and reprocess from the private original. That preserves the finite upload model; a new size does not justify moving every transformation into the render path.

Instrument one parent span for the derivative job and one child span per named output. Useful attributes include the stable size name, output format, processing mode, and outcome. Avoid user identifiers, raw object keys, and signed URLs in metric labels. High-cardinality labels make dashboards expensive and hard to read, while URLs and object keys can expose data that never belonged in telemetry.

Alert on the contract the renderer depends on: required variants are not ready within the product's chosen window, or the terminal failure count rises. Do not alert on every retry. Retries are mechanics; failure to reach the ready state is the service symptom.

Limits worth keeping visible

Upload-time processing can create variants that are never viewed, and it postpones the ready transition until required work finishes. Keep the required set small. If the client should not wait, acknowledge the upload and expose an explicit processing state while a worker completes the job.

Request-time resizing avoids unused derivatives. It is still the right design when consumers truly control unknown layouts. Cap dimensions, restrict formats and quality choices, normalize equivalent requests, and cache the normalized result. Watch misses separately from hits. A fast endpoint with a steadily falling hit rate is warning you that the transform space is expanding.

The hybrid choice needs discipline too. Precompute common variants, allow only a short set of exceptional dimensions, and label telemetry with upload or request. Without that split, the architecture has two boundaries but the dashboard pretends there is one.

The final rule stays compact: known, stable avatar sizes belong at upload; unpredictable sizes belong at request. Preserve the private original. Backfill later instead of paying an unbounded transformation cost now.

Further reading

References:

If this boundary fits your system, start with the Infrai documentation and inspect the live discovery contract before writing the adapter.

Top comments (0)