CattoPic

May 17, 2026 · View on GitHub

A self-hosted image hosting service with automatic format conversion, tag management, and a random image API. Built with Next.js frontend and Cloudflare Workers backend.

中文文档

Architecture

flowchart TB
    subgraph Client["👤 Client"]
        Browser["🌐 Browser"]
        API_Client["📱 API Client"]
    end

    subgraph Vercel["Vercel"]
        NextJS["⚛️ Next.js 16<br/>React 19 + Tailwind CSS"]
    end

    subgraph Cloudflare["Cloudflare Edge Network"]
        subgraph Worker["Worker Runtime"]
            Hono["🔥 Hono API<br/>REST Endpoints"]
        end

        subgraph Storage["Storage Layer"]
            R2[("📦 R2<br/>Object Storage<br/>───────────<br/>• Original images<br/>• WebP versions<br/>• AVIF versions")]
        end

        subgraph Data["Data Layer"]
            D1[("🗄️ D1<br/>SQLite Database<br/>───────────<br/>• Image metadata<br/>• Tags<br/>• API keys")]
            KV[("⚡ KV<br/>Key-Value Store<br/>───────────<br/>• Response cache<br/>• Rate limiting")]
        end

        subgraph Async["Async Processing"]
            Queue["📬 Queues<br/>───────────<br/>• File deletion<br/>• Batch operations"]
            Cron["⏰ Cron Triggers<br/>───────────<br/>• Cleanup expired<br/>• Hourly schedule"]
        end

        subgraph Transform["Image Processing"]
            Images["🖼️ Cloudflare Images<br/>───────────<br/>• WebP conversion<br/>• AVIF conversion<br/>• Quality optimization"]
        end
    end

    Browser -->|"HTTPS"| NextJS
    NextJS -->|"API Requests"| Hono
    API_Client -->|"Direct API"| Hono

    Hono -->|"Read/Write"| R2
    Hono -->|"Query/Update"| D1
    Hono -->|"Cache"| KV
    Hono -->|"Async Tasks"| Queue
    Hono -->|"Transform"| Images

    Queue -->|"Process"| R2
    Cron -->|"Trigger"| Hono
    Images -->|"Output"| R2

    style Cloudflare fill:#f5f5f5,stroke:#f38020,stroke-width:2px
    style Vercel fill:#f5f5f5,stroke:#000,stroke-width:2px
    style Worker fill:#fff3e0,stroke:#f38020
    style Storage fill:#e3f2fd,stroke:#1976d2
    style Data fill:#e8f5e9,stroke:#388e3c
    style Async fill:#fce4ec,stroke:#c2185b
    style Transform fill:#f3e5f5,stroke:#7b1fa2

Component Overview

ComponentServicePurpose
FrontendVercel + Next.jsManagement UI, image browser, upload interface
APICloudflare Worker + HonoRESTful API, authentication, request routing
StorageCloudflare R2Store original and converted images (WebP/AVIF)
DatabaseCloudflare D1Image metadata, tags, API keys (SQLite)
CacheCloudflare KVResponse caching, reduce D1 queries
QueueCloudflare Queues (optional)Async file deletion, batch processing
ImagesCloudflare ImagesOn-the-fly format conversion and optimization
CronCron TriggersScheduled cleanup of expired images

Features

  • Multi-format Support - Upload JPEG, PNG, GIF, WebP, AVIF images
  • Automatic Conversion - Auto-generate WebP and AVIF versions for optimal delivery
  • Tag Management - Organize images with tags, batch operations supported
  • Random Image API - Public API for random images with filtering options
  • Expiry Support - Set expiration time for temporary images
  • Modern UI - Clean management interface with dark mode support

Tech Stack

ComponentTechnology
FrontendNext.js 16, React 19, Tailwind CSS
BackendCloudflare Workers, Hono
StorageCloudflare R2
DatabaseCloudflare D1 (SQLite)
CacheCloudflare KV

Quick Start

Prerequisites

1. Clone and Install

git clone https://github.com/yourusername/cattopic.git
cd cattopic
pnpm install
cd worker && pnpm install

2. Setup Cloudflare Resources

cd worker
pnpm wrangler login

# Create R2 bucket
pnpm wrangler r2 bucket create cattopic-r2

# Create D1 database
pnpm wrangler d1 create CattoPic-D1
# Note the database_id from output

# Create KV namespace
pnpm wrangler kv namespace create CACHE_KV
# Note the id from output

# (Optional) Create Queue - only needed if USE_QUEUE = 'true'
# Requires Cloudflare Workers Paid plan
pnpm wrangler queues create cattopic-delete-queue

# Initialize database schema
pnpm wrangler d1 execute CattoPic-D1 --remote --file=schema.sql

3. Configure Worker

cp wrangler.example.toml wrangler.toml

Edit wrangler.toml with your resource IDs:

[vars]
R2_PUBLIC_URL = 'https://your-r2-domain.com'
# Set to 'true' to use Cloudflare Queues for async R2 deletion
# Set to 'false' or remove for synchronous deletion (no Queue required)
USE_QUEUE = 'false'

[[r2_buckets]]
bucket_name = 'cattopic-r2'

[[d1_databases]]
database_name = 'CattoPic-D1'
database_id = '<your-database-id>'

[[kv_namespaces]]
id = "<your-kv-id>"

# (Optional) Only needed if USE_QUEUE = 'true'
# [[queues.producers]]
# queue = "cattopic-delete-queue"
#
# [[queues.consumers]]
# queue = "cattopic-delete-queue"

4. Deploy Worker

Option A: Manual Deploy

pnpm wrangler deploy

Option B: GitHub Actions (Recommended for Fork users)

GitHub Actions deployment avoids configuration conflicts when syncing upstream.

  1. Create API Token: Go to Cloudflare API Tokens → Create Token → Use "Edit Cloudflare Workers" template

  2. Get Account ID: Run pnpm wrangler whoami to find your Account ID

  3. Configure GitHub Secrets (Settings → Secrets and variables → Actions):

SecretDescription
CLOUDFLARE_API_TOKENYour API Token
CLOUDFLARE_ACCOUNT_IDYour Account ID
WRANGLER_TOMLComplete content of your wrangler.toml file
  1. Trigger: Push to worker/** on main branch, or manually trigger via Actions tab

5. Add API Key

pnpm wrangler d1 execute CattoPic-D1 --remote --command "
INSERT INTO api_keys (key, created_at) VALUES ('your-secure-api-key', datetime('now'));
"

6. Deploy Frontend

Deploy to Vercel with environment variable:

VariableValue
NEXT_PUBLIC_API_URLhttps://your-worker.workers.dev
NEXT_PUBLIC_REMOTE_PATTERNShttps://your-worker.workers.dev,https://r2

Upgrading Existing Deployments

schema.sql is the baseline for new installations. Existing deployments should keep their current D1 database; API keys remain in the api_keys table.

This release adds a deletion_jobs table for reliable R2 cleanup. Existing deployments do not need a manual D1 migration command: the Worker creates the table through the D1 binding the first time delete/cleanup code needs it.

Fork + GitHub Actions

  1. Sync or merge upstream changes into your fork.
  2. Keep your existing GitHub Secrets: CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, and WRANGLER_TOML.
  3. Push to main or run the Deploy Worker workflow manually.
  4. No D1 command is required for this upgrade.

Local Pull + Manual Deploy

git pull
corepack pnpm install --frozen-lockfile
corepack pnpm -C worker install --frozen-lockfile
corepack pnpm -C worker exec tsc --noEmit
corepack pnpm -C worker wrangler deploy

No API key rotation is required. Do not configure an API key as a Worker secret; D1 remains the only API key source.

API Overview

Public Endpoints

Random Image

GET /api/random

Returns a random image. Supports filtering:

# Get random landscape image
curl "https://api.example.com/api/random?orientation=landscape"

# Filter by tags
curl "https://api.example.com/api/random?tags=nature,outdoor"

# Exclude tags
curl "https://api.example.com/api/random?exclude=private"

# Request specific format
curl "https://api.example.com/api/random?format=webp"

# Combine filters
curl "https://api.example.com/api/random?orientation=portrait&tags=cat&format=avif"
ParameterValuesDescription
orientationlandscape, portrait, autoImage orientation (auto detects from User-Agent)
tagscomma-separatedInclude images with ALL specified tags
excludecomma-separatedExclude images with ANY specified tags
formatoriginal, webp, avifResponse format (auto-negotiated if not specified)

Protected Endpoints

All other endpoints require authentication:

Authorization: Bearer <your-api-key>
MethodEndpointDescription
POST/api/upload/singleUpload image
GET/api/imagesList images (paginated)
GET/api/images/:idGet image details
PUT/api/images/:idUpdate image metadata
DELETE/api/images/:idDelete image
GET/api/tagsList all tags
POST/api/tagsCreate tag
PUT/api/tags/:nameRename tag
DELETE/api/tags/:nameDelete tag and associated images
POST/api/tags/batchBatch tag operations

Documentation

Local Development

# Terminal 1: Start worker
cd worker
pnpm dev

# Terminal 2: Start frontend
pnpm dev

Create .env.local:

NEXT_PUBLIC_API_URL=http://localhost:8787

License

GPL-3.0