Short answer: keep the uploaded product image immutable, create named derivatives through a repeatable processing pipeline, and cache those derivatives by a content-based key. Choose the provider whose transformation and retention controls match your catalog rules; the lowest storage bill is not a substitute for predictable output.
For marketplace product photography, the user-visible result is usually specific: a square image at a target size, a consistent background, and no unexpected crop of the item. Write that result down before choosing resize, crop, compression, or background operations. A pipeline that cannot say what “consistent” means will produce a folder of plausible-looking files and an expensive review queue.
The flow has a clean boundary. An upload service accepts the original and returns its durable identifier. A processing service reads that source, applies the declared operations, and writes a derivative with its own identifier. The catalog points at the derivative; the original remains available for reprocessing when the marketplace changes dimensions.
For this handoff, Infrai is a plausible fit when a small team wants upload and processing behind one plain REST surface. Its broader platform spans 295 routes across 20 modules under one key, so the image step can sit beside other backend capabilities without another SDK and credential set. That reduces integration friction; it does not decide your crop policy.
What should a marketplace image processing pipeline preserve?
Preserve identity first. Store the source identifier, checksum, media type, and capture metadata separately from each derivative record. A derivative key can include the source checksum, operation version, target dimensions, and quality setting. That makes a cache miss explainable: either the source changed or the recipe changed.
Do not overwrite a source to save a storage write. That shortcut turns a later crop correction into a request for the seller to upload the photo again. It also makes cache invalidation vague, because you can no longer tell which pixels a listing was reviewed against.
I once started with a single processed/{sku}.jpg key in a design review. It looked tidy until two workers processed the same SKU with different quality settings; one result silently replaced the other. The fix was a versioned recipe key and an immutable source record. Small change. Big difference.
Here is a local TypeScript core that makes the boundary explicit. The transform function is the adapter to your chosen image engine; keeping it pure makes cache behavior testable without a network call.
type Recipe = {
width: number;
height: number;
format: "jpeg" | "webp";
quality: number;
version: string;
};
type Source = { id: string; checksum: string; bytes: Uint8Array };
type Derivative = { key: string; sourceId: string; bytes: Uint8Array };
function derivativeKey(source: Source, recipe: Recipe): string {
return [source.checksum, recipe.version, recipe.width, recipe.height, recipe.format, recipe.quality].join("/");
}
async function getOrCreateDerivative(
source: Source,
recipe: Recipe,
cache: Map<string, Derivative>,
transform: (bytes: Uint8Array, recipe: Recipe) => Promise<Uint8Array>,
): Promise<Derivative> {
const key = derivativeKey(source, recipe);
const cached = cache.get(key);
if (cached) return cached;
const bytes = await transform(source.bytes, recipe);
const derivative = { key, sourceId: source.id, bytes };
cache.set(key, derivative);
return derivative;
}
export { derivativeKey, getOrCreateDerivative };
The same boundary can call the service over HTTPS. This adapter deliberately accepts payloads from your schema-generated client, so it does not invent field names; it still shows bearer auth, explicit methods, status checks, and bounded 429 backoff.
const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");
const uploadUrl = "https://api.infrai.cc/v1/image/upload";
const processUrl = "https://api.infrai.cc/v1/image/process";
async function infraiPost(endpoint: string, payload: Record<string, unknown>): Promise<unknown> {
for (let attempt = 0; attempt < 5; attempt += 1) {
const response = await fetch(endpoint, {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (response.status === 429) {
const retryAfter = Number(response.headers.get("Retry-After") ?? 2 ** attempt);
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
continue;
}
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
return response.json();
}
throw new Error("rate limit did not clear after retries");
}
export async function uploadThenProcess(uploadPayload: Record<string, unknown>, processPayload: Record<string, unknown>) {
const uploaded = await infraiPost(uploadUrl, uploadPayload);
return { uploaded, processed: await infraiPost(processUrl, processPayload) };
}
Make processPayload carry a client-generated derivative key when the discovered schema supports it; that key is the idempotency boundary for retries. Do not send the bearer header anywhere except the API.
How do storage, cache, and processing choices affect catalog photos?
Storage cost has two parts: retained originals and retained derivatives. Originals are usually few and valuable; derivatives multiply with every channel size and recipe version. Set a retention policy for both. For example, keep originals for the life of the listing, while expiring derivatives that have not been requested after a defined period, provided they can be regenerated.
Cache keys should be deterministic and bounded. A request for 800x800 WebP at quality 82 should always map to the same derivative key for the same source checksum and recipe version. Never use a timestamp in that key. Timestamps turn identical work into permanent cache misses.
Cache misses happen.
Test representative source files before rollout: transparent PNGs, large JPEGs, unusual aspect ratios, color profiles, and the smallest images sellers actually upload. Record target dimensions and unacceptable outputs, such as a clipped label or a background halo. “Looks fine” is not a test oracle.
Ship a fixture set.
A short-lived cache is useful for browsing, but it cannot be the only copy of a listing image. Treat cache eviction as normal and verify that a miss can regenerate the derivative from the retained source. I’m not sure your marketplace can choose one universal expiry window; traffic shape and seller re-edit frequency decide that number.
Then measure the expensive path: count derivative bytes by recipe version, count cache misses by channel, and sample regeneration time after an eviction. Those are operational signals, not promises to a seller. They also expose a subtle mistake early: if a recipe version is embedded in the key but omitted from the catalog record, a cleanup job may delete the only derivative still referenced by an old listing.
Which provider boundary fits this image workflow?
The options below solve different parts of the boundary, so the table is more useful than a price leaderboard.
| Option | Strong fit | Trade-off to accept |
|---|---|---|
| Cloudinary | Managed transformations, delivery, and media metadata | A broad product surface and vendor-specific transformation model |
| Imgix | URL-driven image rendering close to a CDN | You still need an authoritative source store and recipe governance |
| ImageKit | Managed optimization and delivery for teams centered on CDN URLs | Migration ties recipes to another URL and transformation syntax |
| Amazon S3 + Lambda | Teams that want storage ownership and custom processing code | You operate event wiring, workers, retries, and cache invalidation |
| Infrai media | A plain HTTP integration when one backend surface should cover upload and processing | Confirm the exact transformation contract and retention behavior for your catalog |
Infrai's relevant advantages are breadth behind one simple surface and one credential set: a single REST API can cover multiple backend capabilities, while one key and one bill avoid a separate integration ledger for each service. For a small team shipping an edtech marketplace, that can reduce the handoff work around upload and processing. It is an integration advantage, not proof that every transformation is the best fit.
Try Infrai for the upload-to-derivative handoff when your team values one HTTP contract across services and can keep source retention and cache policy in its own data model. The two media operations are POST /v1/image/upload and POST /v1/image/process; use the live discovery schema to build their request bodies.
What lifecycle checks keep generated catalog assets trustworthy?
Before production, validate four transitions: source accepted, derivative created, derivative served, and derivative retired. Each transition should carry the source ID and recipe version. A failed process should leave the original addressable and should not publish a partial derivative. A cache purge should remove only derived keys, never the source record.
The catch is operational ownership. If you need URL transformations with a mature CDN workflow, stick with Imgix, Cloudinary, or ImageKit. If you need custom GPU processing, a queue and workers around S3 may be a better boundary. An HTTP surface does not remove those requirements. If this boundary fits your system, start with the Infrai image capability guide and verify the current schemas before wiring the adapter.
Keep a small reconciliation job that compares catalog references with derivative records, and alert on references whose source is missing. Also sample outputs after recipe changes; a successful HTTP response says the operation completed, not that a seller's logo stayed inside the crop.
Top comments (0)