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.
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 | Service | Purpose |
|---|---|---|
| Frontend | Vercel + Next.js | Management UI, image browser, upload interface |
| API | Cloudflare Worker + Hono | RESTful API, authentication, request routing |
| Storage | Cloudflare R2 | Store original and converted images (WebP/AVIF) |
| Database | Cloudflare D1 | Image metadata, tags, API keys (SQLite) |
| Cache | Cloudflare KV | Response caching, reduce D1 queries |
| Queue | Cloudflare Queues | Async file deletion, batch processing |
| Images | Cloudflare Images | On-the-fly format conversion and optimization |
| Cron | Cron Triggers | Scheduled cleanup of expired images |
- 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
| Component | Technology |
|---|---|
| Frontend | Next.js 16, React 19, Tailwind CSS |
| Backend | Cloudflare Workers, Hono |
| Storage | Cloudflare R2 |
| Database | Cloudflare D1 (SQLite) |
| Cache | Cloudflare KV |
- Node.js >= 18
- pnpm package manager
- Cloudflare account
- Vercel account (or any static hosting)
git clone https://github.com/yourusername/cattopic.git
cd cattopic
pnpm install
cd worker && pnpm installcd 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
# Create Queue
pnpm wrangler queues create cattopic-delete-queue
# Initialize database schema
pnpm wrangler d1 execute CattoPic-D1 --remote --file=schema.sqlcp wrangler.example.toml wrangler.tomlEdit wrangler.toml with your resource IDs:
[vars]
R2_PUBLIC_URL = 'https://your-r2-domain.com'
[[r2_buckets]]
bucket_name = 'cattopic-r2'
[[d1_databases]]
database_name = 'CattoPic-D1'
database_id = '<your-database-id>'
[[kv_namespaces]]
id = "<your-kv-id>"
[[queues.producers]]
queue = "cattopic-delete-queue"
[[queues.consumers]]
queue = "cattopic-delete-queue"Option A: Manual Deploy
pnpm wrangler deployOption B: GitHub Actions (Recommended for Fork users)
GitHub Actions deployment avoids configuration conflicts when syncing upstream.
-
Create API Token: Go to Cloudflare API Tokens β Create Token β Use "Edit Cloudflare Workers" template
-
Get Account ID: Run
pnpm wrangler whoamito find your Account ID -
Configure GitHub Secrets (Settings β Secrets and variables β Actions):
| Secret | Description |
|---|---|
CLOUDFLARE_API_TOKEN |
Your API Token |
CLOUDFLARE_ACCOUNT_ID |
Your Account ID |
WRANGLER_TOML |
Complete content of your wrangler.toml file |
- Trigger: Push to
worker/**on main branch, or manually trigger via Actions tab
pnpm wrangler d1 execute CattoPic-D1 --remote --command "
INSERT INTO api_keys (key, created_at) VALUES ('your-secure-api-key', datetime('now'));
"Deploy to Vercel with environment variable:
| Variable | Value |
|---|---|
NEXT_PUBLIC_API_URL |
https://your-worker.workers.dev |
NEXT_PUBLIC_REMOTE_PATTERNS |
https://your-worker.workers.dev,https://r2 |
GET /api/randomReturns 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"| Parameter | Values | Description |
|---|---|---|
orientation |
landscape, portrait, auto |
Image orientation (auto detects from User-Agent) |
tags |
comma-separated | Include images with ALL specified tags |
exclude |
comma-separated | Exclude images with ANY specified tags |
format |
original, webp, avif |
Response format (auto-negotiated if not specified) |
All other endpoints require authentication:
Authorization: Bearer <your-api-key>| Method | Endpoint | Description |
|---|---|---|
| POST | /api/upload/single |
Upload image |
| GET | /api/images |
List images (paginated) |
| GET | /api/images/:id |
Get image details |
| PUT | /api/images/:id |
Update image metadata |
| DELETE | /api/images/:id |
Delete image |
| GET | /api/tags |
List all tags |
| POST | /api/tags |
Create tag |
| PUT | /api/tags/:name |
Rename tag |
| DELETE | /api/tags/:name |
Delete tag and associated images |
| POST | /api/tags/batch |
Batch tag operations |
- Deployment Guide (Chinese)
- Deployment Guide (English)
- API Documentation (Chinese)
- API Documentation (English)
# Terminal 1: Start worker
cd worker
pnpm dev
# Terminal 2: Start frontend
pnpm devCreate .env.local:
NEXT_PUBLIC_API_URL=http://localhost:8787