AI-Generated Images: Store and Retrieve Metadata in Node.js A developer outlined a Node.js pattern for storing AI-generated images in an S3-compatible object store with metadata attached as x-amz-meta- headers, using unique keys of the form images/{generationId}/{sha256}.png rather than friendly filenames. Because the storage layer lacks versioning, object lock and conditional writes, the design keeps searchable fields and the deleteAfter deadline in a database, with a scheduler enforcing exact deletion times that daily lifecycle rules cannot express. The example TypeScript code adds retry with backoff on HTTP 429 responses and records deletion results separately for operators. When generated images have a deletion deadline, an object key represents only half the design. Put the image bytes and metadata together, then keep the deadline in a system that can enforce it and record the decision. For a small Node.js service, an S3-compatible object store is a sensible default. An API with one key across backend capabilities can also fit when you want storage beside other services without assembling another credential set. No magic bucket. The useful before-and-after mental model is simple: before, an image is an opaque blob called output.png ; after, it is an immutable-looking application record with an owner, generation ID, content type, and a deletion date. The storage metadata helps you retrieve context. Your database or scheduler remains the authority for retention. Use a unique key such as images/{generationId}/{sha256}.png . Do not use a friendly filename as the identity. A retry or a second generation can otherwise overwrite the first result, and object versioning is not available in every compatible service. In the system described here, the storage layer has no versioning, object lock, or If-Match conditional write, so strict write coordination belongs outside the bucket. Keep searchable fields in a database: generation ID, account ID, object key, content hash, created time, and deleteAfter . Put small descriptive fields alongside the object as x-amz-meta- headers. That split matters. Listing can filter by prefix, but metadata is not server-side searchable, so a bucket listing is a poor retention index. The deadline needs an owner. A daily lifecycle rule may remove old objects, but it cannot express an hourly deadline when the minimum lifecycle period is one day. For an exact deadline, a scheduler reads the database, verifies the record is due, and performs a delete through the storage API. Record the deletion result separately. This gives operators something better than a mysterious missing file. Here is a compact TypeScript example. It uses one upload and one retrieval operation, carries metadata in headers, checks real HTTP failures, and backs off on rate limiting. The caller supplies a unique key; the storage service is not asked to invent one. js import { readFile, writeFile } from "node:fs/promises"; import { randomUUID } from "node:crypto"; const baseUrl = process.env.INFRAI API BASE URL; const apiKey = process.env.STORAGE API KEY; const bucket = process.env.STORAGE BUCKET ?? "generated-images"; const key = images/${randomUUID }/result.png ; if baseUrl || apiKey throw new Error "INFRAI API BASE URL and STORAGE API KEY are required" ; async function request url: string, init: RequestInit : Promise