DEV Community

Cover image for Building an AI Watermark Remover on Cloudflare Workers (Single-File Architecture)
zhuwei290
zhuwei290

Posted on

Building an AI Watermark Remover on Cloudflare Workers (Single-File Architecture)

AI Watermark Remover

Last week, I launched an AI-powered watermark remover that runs entirely on Cloudflare Workers. In this post, I'll walk you through the architecture, challenges, and lessons learned from building a production-ready SaaS with a single-file backend.

Live Demo: https://watermark-remover-cf.shop

GitHub: https://github.com/zhuwei290/watermark-remover-cf


Why Cloudflare Workers?

I wanted to build a privacy-focused image processing tool where:

  • Images are never stored on servers (processed in-memory)
  • Global low-latency access (edge computing)
  • Minimal operational overhead (no servers to manage)
  • Cost-effective at scale

Cloudflare Workers checked all these boxes. The free tier gives you 100,000 requests/day, which is perfect for an MVP.


Architecture Overview

Single-File Design

The entire backend is one JavaScript file (worker.js, ~2000 lines) that handles:

┌─────────────────────────────────────────┐
│           Cloudflare Worker             │
├─────────────────────────────────────────┤
│  Frontend (HTML/CSS/JS)                 │
│  ├─ Landing Page                        │
│  ├─ Dashboard                           │
│  ├─ Pricing Page                        │
│  └─ FAQ Page                            │
├─────────────────────────────────────────┤
│  API Routes                             │
│  ├─ POST /api/remove (watermark removal)│
│  ├─ GET  /api/user (user info)          │
│  ├─ POST /api/create-order (PayPal)     │
│  └─ POST /api/capture-order (payment)   │
├─────────────────────────────────────────┤
│  OAuth Routes                           │
│  ├─ GET  /auth/google                   │
│  ├─ GET  /auth/callback/google          │
│  └─ GET  /auth/logout                   │
├─────────────────────────────────────────┤
│  KV Storage                             │
│  ├─ SESSIONS (user sessions, 7d TTL)    │
│  └─ USERS (user data & usage stats)     │
└─────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Tech Stack

  • Runtime: Cloudflare Workers
  • Frontend: Vanilla JavaScript + HTML5 + CSS3 (no framework)
  • AI Processing: WaveSpeed AI API v3
  • Authentication: Google OAuth 2.0
  • Payments: PayPal Checkout API v2
  • Storage: Cloudflare KV (sessions + user data)
  • Deployment: Wrangler CLI + GitHub Actions

Key Implementation Details

1. Routing Without a Framework

Since Workers don't come with Express-like routing, I implemented a simple router:

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    const path = url.pathname;

    // Static assets
    if (path === '/' || path === '/pricing' || path === '/faq') {
      return env.ASSETS.fetch(request);
    }

    // API routes
    if (path === '/api/remove') {
      return handleRemoveWatermark(request, env);
    }

    if (path === '/auth/google') {
      return handleGoogleAuth(request, env);
    }

    if (path === '/auth/callback/google') {
      return handleGoogleCallback(request, env);
    }

    // ... more routes

    return new Response('Not Found', { status: 404 });
  }
};
Enter fullscreen mode Exit fullscreen mode

2. Session Management with KV

User sessions are stored in Cloudflare KV with a 7-day TTL:

async function createSession(userId) {
  const sessionId = crypto.randomUUID();
  const session = {
    userId,
    createdAt: Date.now(),
    expiresAt: Date.now() + (7 * 24 * 60 * 60 * 1000) // 7 days
  };

  await env.SESSIONS.put(sessionId, JSON.stringify(session), {
    expirationTtl: 7 * 24 * 60 * 60 // 7 days in seconds
  });

  return sessionId;
}
Enter fullscreen mode Exit fullscreen mode

Sessions are passed via HttpOnly, Secure cookies to prevent XSS attacks.

3. Google OAuth 2.0 Flow

async function handleGoogleAuth(request, env) {
  const state = crypto.randomUUID();

  // Store state in KV to prevent CSRF
  await env.SESSIONS.put(`oauth_state:${state}`, state, {
    expirationTtl: 600 // 10 minutes
  });

  const authUrl = new URL('https://accounts.google.com/o/oauth2/v2/auth');
  authUrl.searchParams.set('client_id', env.GOOGLE_CLIENT_ID);
  authUrl.searchParams.set('redirect_uri', 'https://watermark-remover-cf.shop/auth/callback/google');
  authUrl.searchParams.set('response_type', 'code');
  authUrl.searchParams.set('scope', 'openid email profile');
  authUrl.searchParams.set('state', state);

  return Response.redirect(authUrl.toString());
}
Enter fullscreen mode Exit fullscreen mode

4. AI Watermark Removal (WaveSpeed API)

The WaveSpeed AI v3 API uses an async task + polling pattern:

async function removeWatermark(imageBuffer, env) {
  // Step 1: Submit task
  const submitRes = await fetch('https://api.wavespeed.ai/v3/remove-watermark', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${env.WAVESPEED_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      image: imageBuffer.toString('base64'),
      output_format: 'png'
    })
  });

  const { task_id } = await submitRes.json();

  // Step 2: Poll for completion
  let result;
  for (let i = 0; i < 30; i++) {
    await sleep(2000); // Wait 2 seconds

    const checkRes = await fetch(
      `https://api.wavespeed.ai/v3/tasks/${task_id}`,
      {
        headers: { 'Authorization': `Bearer ${env.WAVESPEED_API_KEY}` }
      }
    );

    const task = await checkRes.json();
    if (task.status === 'completed') {
      result = task.output_image;
      break;
    }
  }

  return result;
}
Enter fullscreen mode Exit fullscreen mode

5. PayPal Payment Integration

async function createPayPalOrder(userId, plan, billingCycle, env) {
  const orderData = {
    intent: 'CAPTURE',
    purchase_units: [{
      amount: {
        currency_code: 'USD',
        value: plan === 'pro' ? '9.9' : '29.9'
      },
      description: `${plan.toUpperCase()} Plan - ${billingCycle}`
    }]
  };

  // Get PayPal OAuth token
  const tokenRes = await fetch('https://api-m.paypal.com/v1/oauth2/token', {
    method: 'POST',
    headers: {
      'Authorization': 'Basic ' + btoa(`${env.PAYPAL_CLIENT_ID}:${env.PAYPAL_SECRET}`),
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: 'grant_type=client_credentials'
  });

  const { access_token } = await tokenRes.json();

  // Create order
  const orderRes = await fetch('https://api-m.paypal.com/v2/checkout/orders', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${access_token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(orderData)
  });

  return await orderRes.json();
}
Enter fullscreen mode Exit fullscreen mode

Challenges & Solutions

Challenge 1: Static Assets in Workers

Problem: Workers are designed for dynamic logic, not serving static files.

Solution: Use Wrangler's [assets] configuration:

# wrangler.toml
[assets]
directory = "./public"
Enter fullscreen mode Exit fullscreen mode

This automatically serves static files from the ./public directory.

Challenge 2: Large Image Processing

Problem: Workers have a 128MB memory limit per request.

Solution:

  • Stream images instead of loading entirely into memory
  • Enforce 10MB upload limit on the client side
  • Convert large images to compressed formats before processing

Challenge 3: Payment State Management

Problem: PayPal webhooks can be delayed; users expect instant upgrades.

Solution:

  • Frontend polls payment status every 3 seconds
  • Order state stored in KV with 1-hour TTL
  • Webhook serves as backup to update user status

Cost Breakdown

Monthly Operating Costs (at 1000 users/month)

Service Cost
Cloudflare Workers $0 (free tier)
Cloudflare KV $0 (free tier)
WaveSpeed AI ~$120 (10,000 images @ $0.012/image)
PayPal Fees ~$150 (3.9% + $0.30 per transaction)
Total ~$270/month

Revenue Potential

If 5% of users convert to Pro ($9.9/month):

  • 50 paying users × $9.9 = $495/month
  • Profit: ~$225/month (45% margin)

Lessons Learned

1. Single-File Architecture Works (for MVPs)

Having everything in one file made deployment trivial and debugging straightforward. However, I'm already hitting the limits—2000 lines is getting hard to navigate. Next iteration might split into modules.

2. Edge Computing is Great for Global Users

Users from Asia, Europe, and US all report sub-100ms latency. Cloudflare's edge network is a huge win for user experience.

3. OAuth + Payments = Complexity

Integrating Google OAuth and PayPal took 60% of development time. If I were to do it again, I'd consider using a SaaS like Clerk or Stripe that bundles auth + payments.

4. Privacy is a Selling Point

Emphasizing "images are never stored" resonates with users. Many competitors store uploads for "processing optimization"—this is a key differentiator.


What's Next?

  • [ ] Add batch processing (multiple images at once)
  • [ ] Implement rate limiting per user tier
  • [ ] Add image comparison slider (before/after)
  • [ ] Create Chrome extension
  • [ ] Migrate sensitive config to Cloudflare Secrets

Try It Out

I'd love your feedback!

🔗 Live Demo: https://watermark-remover-cf.shop

💻 Source Code: https://github.com/zhuwei290/watermark-remover-cf

Questions? Drop them in the comments! 👇


Originally published at watermark-remover-cf.shop

Top comments (0)