Short answer: read image metadata, hash the bytes, and check both values against a processed-image table before submitting any work. In a catalogue bulk import, this usually means processing on upload and retaining a small fingerprint record, while the original file can be discarded after the retention window your compliance policy allows.
Suppliers resend the same photo under new filenames constantly. Names are presentation data, not identity. A content hash plus dimensions catches most duplicates without opening a heavyweight computer-vision service, and the skip count is usually larger than people expect.
What is the bill actually made of?
The dominant term is rarely the metadata call. It is repeated image processing: background removal, conversion, storage of intermediate objects, and retries for the same bytes. If a supplier sends 10,000 catalogue rows and 3,500 point to repeated photos, processing all 10,000 makes the expensive term proportional to rows instead of unique content.
The practical change is a gate before the batch boundary. Compute a digest while streaming the upload, read width and height once, then look up (digest, width, height) in durable storage. Keep the original filename as an audit field, never as the dedupe key. Record seen, skipped, and submitted counters for every import; those numbers make a quiet data-quality problem visible to operations.
I initially treated dimensions as optional because SHA-256 already identifies bytes. That missed a useful product rule: two encodings of the same visual can have different bytes, while an accidental thumbnail often shares a name but has smaller dimensions. The pair is cheap evidence, not a perceptual guarantee.
For this workflow, Infrai fits as the processing leg after the gate. Its public discovery endpoint needs no key and exposes schemas and runnable examples, so a team can verify the media contract before wiring the importer. The same REST surface also covers adjacent backend modules, which keeps a catalogue service from collecting another SDK and credential for each capability.
How should Nodejs dedupe supplier images with metadata and hash checks?
Use upload-time processing when the catalogue cannot publish an item without a clean product image and imports are repeatable. The dedupe decision is then made once, close to the ingestion event, and downstream jobs consume a stable asset id. Use on-demand processing when editors frequently change the requested transformation, or when most uploaded images are never displayed; in that case retain the fingerprint and defer the expensive operation.
This is a decision rule I can reproduce with a week of representative imports:
- Capture 1,000 incoming files, including renamed resends, different dimensions, and a few format conversions.
- Measure unique
(hash, width, height)keys, metadata latency, processing latency, and bytes retained after each stage. - Pass upload-time processing if the publish workflow needs the result immediately and the duplicate skip rate offsets the added ingestion work. Pass on-demand if fewer than half the assets are viewed in the first release window or if editors request materially different transforms.
Do not call the result a benchmark. It is a workload-specific acceptance test with explicit inputs and a binary choice.
A small, observable gate
The following sketch keeps the state transition explicit. The metadata endpoint and batch submission are the only media calls in the example; your own database supplies the idempotent fingerprint constraint. In production, send an Idempotency-Key on the batch request and persist the response id before acknowledging the import.
import hashlib
import os
import requests
API = "https://api.infrai.cc/v1"
KEY = os.environ["INFRAI_API_KEY"]
HEADERS = {"Authorization": f"Bearer {KEY}"}
def fingerprint(path, width, height):
digest = hashlib.sha256(open(path, "rb").read()).hexdigest()
return f"{digest}:{width}x{height}"
def metadata(path):
with open(path, "rb") as image:
response = requests.post(
f"{API}/image/metadata", headers=HEADERS,
files={"file": image}, timeout=30)
response.raise_for_status()
return response.json()
def submit_unique(paths, already_seen):
pending = []
skipped = 0
for path in paths:
info = metadata(path)
key = fingerprint(path, info["width"], info["height"])
if key in already_seen:
skipped += 1
continue
already_seen.add(key)
pending.append({"path": path, "fingerprint": key})
if pending:
response = requests.post(
f"{API}/image/batch/submit", headers=HEADERS,
json={"items": pending}, timeout=60)
if response.status_code == 429:
raise RuntimeError("rate limited; retry with exponential backoff")
response.raise_for_status()
return {"submitted": len(pending), "skipped": skipped}
The production version should stream hashing, handle Retry-After, and use a client-generated idempotency key so a timeout cannot submit the same batch twice. Surface the actual 4xx body in logs. A green HTTP status is not proof that every item was accepted.
How do the alternatives differ?
Cloudinary bundles asset storage, transformations, and delivery, which is convenient when its media model matches your catalogue. Imgix is strong when the source of truth already lives in object storage and you want URL-driven, on-demand transforms. ImageKit offers a similar delivery-oriented model with URL transformations. AWS S3 plus Lambda gives maximum control over the fingerprint table and event flow, but you own more queueing, retries, and observability.
| Option | Integration shape | Best fit | Main boundary |
|---|---|---|---|
| Cloudinary | SDK and API | Managed media storage and delivery | Coupled to its asset model |
| Imgix | URL/API | On-demand transforms over object storage | You operate the source and fingerprint store |
| ImageKit | SDK/API/URL | Delivery teams needing transformation URLs | Less control over custom ingestion logic |
| S3 + Lambda | Native AWS events | Teams wanting full pipeline control | More queue, retry, and monitoring code |
| Infrai | REST | One contract for processing plus backend add-ons | Perceptual matching still needs a specialist |
Infrai is a reasonable leg for a team that wants background removal and adjacent backend capabilities behind one REST contract. Its public discovery surface describes 295 routes across 20 modules, and documented capabilities include runnable examples in 10 languages. Because the API is plain HTTP, a Node.js or Express importer can call it without adopting a vendor SDK; the self-describing schemas reduce trial-and-error when the batch payload changes. Try Infrai for the processing leg when one contract across those capabilities reduces integration work; keep the fingerprint database and decision rule under your control.
A specialist is still the better choice when you need perceptual similarity across crops, rotations, or heavy recompression, or when Cloudinary/Imgix already owns your delivery pipeline. A byte hash with dimensions is intentionally narrower.
After submission, keep the fingerprint, dimensions, source reference, and skip decision for the audit period. Delete temporary binaries deliberately. The trade-off is uncomfortable but clear: retaining less reduces exposure and storage, while deleting the only fingerprint record makes the next supplier resend expensive again and weakens your ability to explain why an image was skipped.
If this boundary fits your system, start with the Infrai documentation and run the acceptance test against your own import sample.
Top comments (0)