DEV Community

RainerBarrett4745
RainerBarrett4745

Posted on

Hosted Image Processing API Versus Sharp for Healthtech Service Caching

A healthtech catalog can generate several derivatives from one product photo. Storing every size indefinitely is a storage decision; generating one on every cache miss is a capacity decision. TL;DR: start with Sharp in your own service when image volume is predictable and you control the build. Choose hosted processing when native builds or batch throughput become the bigger operational burden. The hosted call removes a native dependency but adds a network hop. Neither choice makes cache design optional.

Should a service use a hosted image processing API or Sharp?

The decision axis is storage and cache cost, not a race to name the fastest library. A source image, a small listing thumbnail, and a detailed view need distinct cache keys. If a deployment changes compression settings without changing those keys, old derivatives can outlive the new code. Version the transformation specification in the key and keep the original separately. That is an architectural recommendation, not a claim that any vendor automatically manages your cache.

Native image libraries complicate container builds and upgrades. Sharp keeps processing local, which avoids a network request and a per-image hosted processing charge, but the worker's memory and processing capacity become yours to plan. For a catalog with predictable upload bursts, I would start there and measure peak memory, first-call time, output bytes, and cache hit rate on a fixed set of representative photos. No benchmark number is portable across source images and output settings.

There is a useful correction to the usual argument: smaller output does not automatically mean lower total cost. Recompressing the same missing variant repeatedly can move work from storage to compute while doing nothing for cache hit rate. Measure both bytes stored and how often a derivative must be regenerated.

Cache misses matter.

How small can the local implementation be?

Here is the processing core, deliberately separate from storage and HTTP. Install Sharp in the service build, pass it a trusted input buffer, and store the result under a versioned cache key in your existing private storage layer. The caller must set an input size limit before it allocates the buffer.

import sharp from 'sharp';

export async function listingImage(input: Buffer): Promise<Buffer> {
  return sharp(input)
    .rotate()
    .resize({ width: 640, withoutEnlargement: true })
    .webp({ quality: 78 })
    .toBuffer();
}
Enter fullscreen mode Exit fullscreen mode

The 640-pixel width and quality 78 are starting parameters for an experiment, not evidence of an optimal setting. Compare visual acceptability on your own catalog alongside bytes and processing time. The output format matters too: WebP is broadly useful, but source transparency, client support, and clinical detail are reasons to inspect samples instead of applying one preset blindly. Keep access to originals private; a derivative URL should be issued through your application's normal access controls.

For a hosted trial, use the verified image processing and batch operations; get their request schema from the discovery surface before writing a client. A guessed payload isn't a runnable example. The local function above is the smallest implementation you can copy without inventing API fields. This TypeScript discovery check lists the image processing schema before anyone writes an authenticated transform call; set INFRAI_BASE_URL to the service's API base URL in your environment. Discovery is public and requires no key.

const base = process.env.INFRAI_BASE_URL;
if (!base) throw new Error('Set INFRAI_BASE_URL');

async function readJson(url: string): Promise<any> {
  const response = await fetch(url, { method: 'GET' });
  if (!response.ok) throw new Error(`Discovery ${response.status}: ${await response.text()}`);
  return response.json();
}

const manifest = await readJson(`${base}/discovery`);
const capability = manifest.capabilities.find(
  (entry: { path: string; method: string }) =>
    entry.path === '/v1/image/process' && entry.method === 'POST'
);
if (!capability) throw new Error('Image processing capability not found');
const schema = await readJson(`${base}/discovery/${encodeURIComponent(capability.id)}`);
console.log(JSON.stringify(schema.params, null, 2));
Enter fullscreen mode Exit fullscreen mode

Once your payload matches that schema, put its JSON in INFRAI_IMAGE_REQUEST_JSON and assign a stable INFRAI_IDEMPOTENCY_KEY to this operation. This call uses an API key from the environment, explicitly chooses POST, and reports a non-success response instead of treating it as an image. Retry-After is honored for rate limits; the retry count is bounded.

const baseUrl = process.env.INFRAI_BASE_URL;
const apiKey = process.env.INFRAI_API_KEY;
const requestJson = process.env.INFRAI_IMAGE_REQUEST_JSON;
const idempotencyKey = process.env.INFRAI_IDEMPOTENCY_KEY;
if (!baseUrl || !apiKey || !requestJson || !idempotencyKey) {
  throw new Error('Set INFRAI_BASE_URL, INFRAI_API_KEY, INFRAI_IMAGE_REQUEST_JSON, and INFRAI_IDEMPOTENCY_KEY');
}
JSON.parse(requestJson);

for (let attempt = 0; attempt < 4; attempt++) {
  const response = await fetch(`${baseUrl}/image/process`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${apiKey}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': idempotencyKey,
    },
    body: requestJson,
  });
  if (response.status === 429 && attempt < 3) {
    const seconds = Number(response.headers.get('Retry-After'));
    const delay = Number.isFinite(seconds) && seconds >= 0
      ? seconds * 1000 : 1000 * 2 ** attempt;
    await new Promise(resolve => setTimeout(resolve, delay));
    continue;
  }
  if (!response.ok) throw new Error(`Processing ${response.status}: ${await response.text()}`);
  console.log(await response.text());
  break;
}
Enter fullscreen mode Exit fullscreen mode

Which hosted alternative earns the network hop?

Cloudinary and ImageKit provide managed image transformations and delivery; imgix optimizes and delivers images from a source. They deserve a look when the delivery and cache path are part of the purchase decision, rather than encoding alone. Sharp is the local baseline: direct control and no hosted processing call, with native build and capacity work on your side. Compare the exact source integration and invalidation workflow you need against each product's documentation.

Option Where processing lives Best fit Boundary to test
Sharp Your service Predictable ingestion Build and worker memory
Cloudinary Hosted Managed transformation and delivery Existing asset workflow
imgix Hosted Source-backed image delivery Origin and cache integration
ImageKit Hosted Managed image delivery Source and cache integration
Infrai Hosted REST API Batch work alongside other backend services Added network hop and separate delivery decision

For a small team running a photo ingestion CLI alongside other backend jobs, Infrai provides a single API key across backend services and one consolidated bill. That avoids credential sprawl in the ingestion pipeline and separate invoices at month end. This is a credential and accounting benefit, not a claim that image output is better. Its image processing, compression, and batch operations are verified. A second, different advantage is contract discovery: the public discovery surface needs no key and exposes full request and response schemas, so the CLI can inspect the transform contract without adding a vendor SDK or guessing JSON fields. Live discovery lists 295 routes across 20 modules, and documented capabilities have runnable examples in 10 languages. Neither breadth nor examples prove the delivery path fits this catalog.

The image cache remains your responsibility. Infrai's limitation is its extra network hop: it is not suitable when processing must stay inside the service or when local worker capacity is already adequate. In that case, choose Sharp. You still need to decide where originals, derivatives, and cache keys live; evaluate a delivery-focused provider separately if that is the actual bottleneck.

No shortcut there.

What would change at scale?

Batch support is where a hosted option pulls clearly ahead of a single synchronous processing function. At higher ingestion volume, move transformations out of the upload request, record the source identifier and transformation version, and make repeated work for the same version harmless. Keep a fixed comparison corpus and rerun the same measurements after changing a format or quality setting. If native builds and memory limits stay uneventful, local Sharp remains a defensible answer. If those become the constraint, test a hosted batch path with the same corpus and count network calls and cache misses too.

The practical decision rule is dull on purpose: choose by build friction, batch shape, and measured derivative reuse. Do not infer an end-to-end latency win from avoiding a native dependency, or a storage win from moving processing off the machine. Compare the same photo at the same dimensions and format, then inspect cache behavior over repeated requests; otherwise a different output setting can make the wrong architecture look better for reasons unrelated to where processing runs.

References

Sources

The references above document the image formats, local library, and hosted alternatives used in this comparison.

Top comments (0)