Skip to content

Maxservais/batch-image-edit

Repository files navigation

Batch Image Edit

Status: Development / Prototype -- This project was built as an exploration of Cloudflare's full-stack platform (Workers, Durable Objects, Queues, Workflows, R2, D1). It is not production-hardened and is shared as-is for reference and learning purposes.

A full-stack application for batch AI image editing. Users upload images, provide a prompt, and the system processes them in parallel using OpenAI's gpt-image-1 model -- with a credit-based billing system, real-time progress tracking, and batch history.

Goal

The goal was to build a non-trivial, end-to-end SaaS prototype that exercises as many Cloudflare developer platform primitives as possible in a single project:

  • Durable Objects for strongly consistent, per-user credit balances and per-batch coordination state
  • Queues for decoupling request handling from async image processing
  • Workflows for multi-step image editing with built-in retries
  • R2 for object storage (uploaded + edited images)
  • D1 for relational data (users, sessions, credit transactions, batch history)
  • Workers as the compute layer for everything

The secondary goal was to wire up a real credit-based billing flow (via Polar.sh) so the system could theoretically charge for usage.

Architecture

┌─────────────────────┐         ┌──────────────────────────────────────────────┐
│   React Frontend    │         │          Cloudflare Workers (Hono)           │
│  (Cloudflare Workers) │────────▶│                                              │
│                     │  service│  ┌─────────┐  ┌─────────────────────────┐    │
│  React Router       │ binding │  │  Auth   │  │   Routes                │    │
│  TailwindCSS        │         │  │(Better- │  │  /api/images/generate   │    │
│  Zustand            │         │  │  Auth)   │  │  /api/batch/:id/status  │    │
│  TanStack Query     │         │  └─────────┘  │  /api/billing/*         │    │
│  Radix UI           │         │               │  /api/files/*           │    │
└─────────────────────┘         │               │  /api/polar/webhooks    │    │
                                │               └────────┬────────────────┘    │
                                │                        │                     │
                                │         ┌──────────────┼──────────────┐      │
                                │         ▼              ▼              ▼      │
                                │  ┌────────────┐ ┌────────────┐ ┌──────────┐ │
                                │  │UserCreditDO│ │BatchCoord  │ │  Queues  │ │
                                │  │            │ │    DO      │ │          │ │
                                │  │ Per-user   │ │ Per-batch  │ │ image-   │ │
                                │  │ credit     │ │ progress   │ │ process  │ │
                                │  │ balance    │ │ tracking   │ │ queue    │ │
                                │  │ (atomic)   │ │ (SQLite)   │ │          │ │
                                │  └────────────┘ └────────────┘ │ credit-  │ │
                                │                                │ update   │ │
                                │                                │ queue    │ │
                                │                                └────┬─────┘ │
                                │                                     │       │
                                │                                     ▼       │
                                │                              ┌────────────┐ │
                                │                              │ Workflow:  │ │
                                │                              │ ProcessUrl │ │
                                │                              │            │ │
                                │                              │ 1. Verify  │ │
                                │                              │ 2. Fetch   │ │
                                │                              │ 3. AI Edit │ │
                                │                              │ 4. Save    │ │
                                │                              │ 5. Report  │ │
                                │                              └────────────┘ │
                                │                                             │
                                │  ┌──────┐  ┌──────┐  ┌──────────────────┐   │
                                │  │  D1  │  │  R2  │  │  External APIs   │   │
                                │  │(SQL) │  │(Blob)│  │  OpenAI, Polar,  │   │
                                │  │      │  │      │  │  Resend           │   │
                                │  └──────┘  └──────┘  └──────────────────┘   │
                                └──────────────────────────────────────────────┘

Request flow

  1. User uploads images to R2 via the file upload API
  2. User submits a batch with image keys + a prompt
  3. Server atomically consumes credits from the UserCreditDO (1 credit per image)
  4. BatchCoordinatorDO initializes tracking state for the batch
  5. Individual image tasks are enqueued to the image processing queue
  6. Queue consumer triggers a Cloudflare Workflow per image
  7. The workflow fetches the image from R2, calls OpenAI to edit it, saves the result back to R2, and reports status to the BatchCoordinatorDO
  8. Frontend polls /api/batch/:id/status to show real-time progress
  9. When all images are done, the DO persists final state to D1 for history

Credits & billing

  • UserCreditDO (Durable Object) is the source of truth for each user's credit balance -- atomic reads and writes, no race conditions
  • New users get 10 free credits on sign-up
  • Credit packs are purchased via Polar.sh (one-time products)
  • Polar sends an order.succeeded webhook, which enqueues a message to the credit update queue, which calls UserCreditDO.addCredits()
  • All credit changes are logged to a credit_transactions table in D1 for auditability

Batch coordination

  • Each batch gets its own BatchCoordinatorDO instance (keyed by batchId)
  • The DO uses SQLite storage (ctx.storage.sql) with two tables: batch_info and image_info
  • Tracks per-image status (pending / processing / completed / failed)
  • Detects batch completion and persists final results to D1

Tech stack

Layer Technology
Frontend React Router, TailwindCSS v4, Radix UI, Zustand, TanStack Query
Backend Hono (on Cloudflare Workers)
Database Cloudflare D1 (via Drizzle ORM)
Object storage Cloudflare R2
State coordination Cloudflare Durable Objects (2: UserCreditDO, BatchCoordinatorDO)
Async processing Cloudflare Queues (2) + Cloudflare Workflows
Auth BetterAuth (self-hosted, Google OAuth + email/password)
Payments Polar.sh
AI OpenAI API (gpt-image-1)
Email Resend
Monorepo pnpm workspaces + Turborepo

Project structure

batch-image-edit/
├── apps/
│   ├── server/              # Hono API (Cloudflare Worker)
│   │   ├── src/
│   │   │   ├── db/          # Drizzle schema & migrations
│   │   │   ├── durable-objects/
│   │   │   │   ├── user-credit.do.ts
│   │   │   │   └── batch-coordinator.do.ts
│   │   │   ├── routes/      # API routes
│   │   │   ├── services/    # AI, storage, email, Firecrawl
│   │   │   ├── workers/     # Queue consumers
│   │   │   └── workflows/   # Cloudflare Workflows
│   │   └── wrangler.jsonc
│   └── web/                 # React frontend (Cloudflare Workers)
│       ├── app/
│       │   ├── routes/      # Page routes
│       │   ├── components/  # UI components
│       │   ├── stores/      # Zustand stores
│       │   └── hooks/       # Custom hooks
│       └── wrangler.jsonc
├── packages/
│   └── shared/              # Shared types & utilities
├── turbo.json
└── pnpm-workspace.yaml

Getting started

Prerequisites

  • Node.js 20+
  • pnpm
  • A Cloudflare account with access to Workers, D1, R2, Queues, Durable Objects, and Workflows
  • API keys for: OpenAI, Polar.sh, Resend (and optionally Firecrawl)

Setup

pnpm install

Copy the relevant .dev.vars files into apps/server/ and apps/web/ with your API keys and secrets (these are not committed to the repo).

Development

pnpm run dev

This starts both the server (Wrangler) and web (Vite) dev servers via Turborepo.

Deployment

pnpm run deploy

Deploys both workers to Cloudflare. Resources declared in wrangler.jsonc (D1 databases, R2 buckets, Queues, Durable Objects, etc.) are automatically provisioned by Wrangler on first deploy.

License

MIT

About

Batch AI image editing on Cloudflare Workers — Durable Objects, Queues, Workflows, R2, D1

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors