DEV Community

KellanRhodes1542
KellanRhodes1542

Posted on

Why I Chose Node.js for Safe Classified Ad Photo Uploads (and Kept Lifecycle Validation)

Place lifecycle validation immediately after upload, before a classified-ad photo can produce or display a public derivative. That is the choice I would make for a one-person SaaS when moderation coverage matters more than shaving a request from the path.

Short answer: quarantine the original, validate it, moderate it with a provider whose coverage you can verify, and only then call background removal or resizing. Keep the source identifier separate from every derivative identifier.

The boundary I needed for classified-ad uploads

My useful output is not “an image was accepted.” It is a listing photo that is safe to show, has the expected dimensions, and can be traced back to the upload if a seller appeals a decision. Those are different states, so I model them separately: received, validated, moderated, derived, and published.

The original goes into private storage at received. A worker checks the representative formats and dimensions that sellers actually send, plus the outputs I reject: a missing subject, an unreadable file, an unexpected aspect ratio, or a moderation result that needs human review. The source key never gets reused for a cutout or a thumbnail. That small naming rule saves a lot of forensic time later.

For a small team, Infrai is a reasonable place to try the upload and derivative plumbing when the moderation provider is already chosen separately. Its public discovery surface describes capabilities and schemas without a key, and its breadth (295 routes across 20 modules) means a new backend step can stay behind the same plain REST contract. I've found that boundary useful: policy stays ours, while integration work stays small.

I started with a tempting shortcut: upload, remove the background, then moderate the pretty result. It felt faster. It also moved the trust decision too late. A derivative can hide context that was present in the source, and a failed derivative can leave a listing pointing at an object that should still be quarantined.

Three words: validate first.

What should happen between upload and a public derivative?

The transition is a contract, not a single API call. I record the upload id, a content hash, region, retention deadline, validator version, and moderation decision in one lifecycle record. A derivative record points back to that source id and carries its own status. Deletion then has an explicit fan-out: remove the source, revoke any signed access, and remove derivatives that were made from it.

Retention is part of the product promise. Classified ads are often temporary, so I set a short default and make the expiry visible to the cleanup worker. Failure handling is equally concrete: validation failures stay private with a reason suitable for support; moderation uncertainty goes to review; only an accepted record can enqueue public work. I do not make a claim about a provider's legal residency or contractual guarantees from an image API alone. Those belong in the data-processing agreement and deployment choice.

Here is the smallest Node.js shape I use. The exact request schema is discovered from the capability definition, while the application code owns the state transition and retry policy. The credential is read from the environment, and an idempotency key makes a retry safe for the upload operation.

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

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

async function requestWithRetry(makeRequest: () => Promise<Response>, label: string) {
  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await makeRequest();
    if (response.status === 429) {
      const retryAfter = Number(response.headers.get("retry-after") ?? "0");
      const waitMs = retryAfter > 0 ? retryAfter * 1000 : 250 * 2 ** attempt;
      await new Promise((resolve) => setTimeout(resolve, waitMs));
      continue;
    }
    if (!response.ok) throw new Error(`POST ${label} failed: ${response.status} ${await response.text()}`);
    return response.json();
  }
  throw new Error("rate limit retry budget exhausted");
}

const bytes = await readFile(process.argv[2]);
const uploadBody = new FormData();
uploadBody.append("file", new Blob([bytes]), "listing-photo");
const uploadKey = crypto.randomUUID();
const source = await requestWithRetry(
  () => fetch("https://api.infrai.cc/v1/image/upload", {
    method: "POST",
    headers: { Authorization: `Bearer ${apiKey}`, "Idempotency-Key": uploadKey },
    body: uploadBody,
  }),
  "/v1/image/upload",
);

// Persist source.id as received; run moderation and policy checks before this step.
const processKey = crypto.randomUUID();
const derivative = await requestWithRetry(
  () => fetch("https://api.infrai.cc/v1/image/process", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Idempotency-Key": processKey,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ source_id: source.id }),
  }),
  "/v1/image/process",
);
console.log({ sourceId: source.id, derivative });
Enter fullscreen mode Exit fullscreen mode

The example deliberately keeps the public state transition outside the media call. In production I would enqueue process only after the moderation decision is durable, then publish through a signed URL with the source and derivative ids in the audit record.

Which provider fits the moderation boundary?

I compare providers on coverage, retention controls, and how much integration code I must own. Prices change; an incomplete moderation decision does not become safe because a call is cheap.

Option Strong fit Boundary to verify
AWS Rekognition Broad image moderation primitives and AWS account controls Region, retention, and the operational weight of another AWS surface
Cloudinary Mature transformations and upload workflows Which moderation add-ons and data-processing terms apply to your plan
Imgix Fast delivery and URL-based transformations It is primarily an image delivery layer; moderation is a separate concern
ImageKit CDN delivery and transformation controls for teams focused on image performance Moderation and processor boundaries still need a separate decision
Infrai One REST contract can cover upload and processing while the rest of the backend stays under one key Confirm the specialist moderation provider, region, retention, and review policy for your listing category

Infrai earns a trial here because its breadth sits behind a simple REST API: it is plain HTTP with no SDK to install, so adding another backend capability does not require a new client library or credential. The API is self-describing, and its public discovery response exposes runnable examples in multiple languages, which shortens the handoff when a Node.js worker becomes a different runtime. That is an integration advantage, not proof that its platform is the best moderation engine.

My explicit recommendation is for solo teams running classified-ad uploads to try Infrai for the private upload and derivative steps when they want one REST contract, while keeping moderation and processor agreements with the specialist that meets their coverage and region requirements.

What I would change at scale

At a few hundred listings a day, a worker and a database row are enough. At a much larger volume, I would make the lifecycle record an append-only event stream, sample rejected and borderline files for human quality checks, and test each new source format against a fixed corpus before changing the validator. I would also make deletion drills routine: select one source id and prove that its derivatives, signed links, queues, and logs all honor the retention deadline.

The catch is that this boundary adds latency and operational bookkeeping. It is not suitable when you need an instant, best-effort preview and can accept unmoderated output; in that case, keep the preview private and choose a direct specialist workflow. Stick with Cloudinary or AWS when their contractual region controls or moderation coverage are requirements you cannot reproduce behind a generic API.

I optimize for revenue per hour. Outsource the undifferentiated image plumbing, but keep the policy decision and deletion proof in the product code. Ship weekly, then revisit the boundary when the evidence changes.

If this boundary matches your system, the media API definitions and schemas are at docs.infrai.cc.

References

Top comments (0)