Skip to content

Backend API Architecture ​

BrowseGenius uses a Cloudflare Workers backend for cloud storage, sync, and user management. The backend is built with Hono framework and uses Cloudflare's edge infrastructure.

Technology Stack ​

Cloudflare Workers ​

  • Serverless edge compute platform
  • Deployed globally across 300+ data centers
  • Sub-50ms response times worldwide
  • Automatic scaling and zero cold starts

Cloudflare D1 ​

  • SQLite-based relational database
  • Automatic replication and backups
  • SQL queries with time-travel
  • 10GB storage per database

Cloudflare R2 ​

  • S3-compatible object storage
  • Zero egress fees
  • Automatic CDN distribution
  • Optimized for large files (screenshots, videos)

Hono Framework ​

  • Lightweight TypeScript web framework
  • Express-like routing API
  • Built-in middleware support
  • Optimized for edge computing

Database Schema ​

Core Tables ​

users ​

Stores user accounts and subscription info:

sql
CREATE TABLE users (
  id TEXT PRIMARY KEY,
  email TEXT UNIQUE NOT NULL,
  password_hash TEXT NOT NULL,
  name TEXT,
  subscription_tier TEXT DEFAULT 'free',
  api_credits INTEGER DEFAULT 100,
  created_at INTEGER DEFAULT (unixepoch()),
  last_login INTEGER
);

projects ​

User projects organized by hostname:

sql
CREATE TABLE projects (
  id TEXT PRIMARY KEY,
  user_id TEXT NOT NULL,
  name TEXT NOT NULL,
  hostname TEXT NOT NULL,
  description TEXT,
  created_at INTEGER DEFAULT (unixepoch()),
  updated_at INTEGER DEFAULT (unixepoch()),
  last_accessed_at INTEGER DEFAULT (unixepoch()),
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

CREATE INDEX idx_projects_user_id ON projects(user_id);
CREATE INDEX idx_projects_hostname ON projects(user_id, hostname);

test_plans ​

Saved test plans with test cases:

sql
CREATE TABLE test_plans (
  id TEXT PRIMARY KEY,
  project_id TEXT NOT NULL,
  user_id TEXT NOT NULL,
  name TEXT NOT NULL,
  description TEXT,
  capture_ids TEXT,
  test_cases TEXT NOT NULL,
  created_at INTEGER DEFAULT (unixepoch()),
  updated_at INTEGER DEFAULT (unixepoch()),
  last_run_at INTEGER,
  FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE,
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

CREATE INDEX idx_test_plans_project ON test_plans(project_id);
CREATE INDEX idx_test_plans_user ON test_plans(user_id);

captures ​

Screenshot metadata with R2 storage keys:

sql
CREATE TABLE captures (
  id TEXT PRIMARY KEY,
  project_id TEXT,
  user_id TEXT NOT NULL,
  image_r2_key TEXT NOT NULL,
  url TEXT NOT NULL,
  title TEXT,
  notes TEXT,
  captured_at INTEGER DEFAULT (unixepoch()),
  dom_snapshot TEXT,
  FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE SET NULL,
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

CREATE INDEX idx_captures_project ON captures(project_id);
CREATE INDEX idx_captures_user ON captures(user_id);

extension_api_keys ​

API keys for browser extension authentication:

sql
CREATE TABLE extension_api_keys (
  id TEXT PRIMARY KEY,
  user_id TEXT NOT NULL,
  key_prefix TEXT NOT NULL,
  key_hash TEXT NOT NULL,
  last_four TEXT NOT NULL,
  name TEXT,
  last_used INTEGER,
  created_at INTEGER DEFAULT (unixepoch()),
  revoked_at INTEGER,
  FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);

CREATE INDEX idx_extension_keys_hash ON extension_api_keys(key_hash);

Flow Execution Tables ​

flows ​

AI-discovered user flows:

sql
CREATE TABLE flows (
  id TEXT PRIMARY KEY,
  user_id TEXT NOT NULL,
  name TEXT NOT NULL,
  description TEXT,
  base_url TEXT NOT NULL,
  start_url TEXT,
  goal TEXT,
  steps TEXT,
  selectors TEXT,
  dom_snapshot TEXT,
  status TEXT DEFAULT 'draft',
  version INTEGER DEFAULT 1,
  success_rate REAL DEFAULT 0,
  total_executions INTEGER DEFAULT 0,
  created_at INTEGER DEFAULT (unixepoch()),
  updated_at INTEGER DEFAULT (unixepoch())
);

flow_executions ​

Execution history with results:

sql
CREATE TABLE flow_executions (
  id TEXT PRIMARY KEY,
  flow_id TEXT NOT NULL,
  flow_version INTEGER NOT NULL,
  user_id TEXT NOT NULL,
  status TEXT DEFAULT 'running',
  started_at INTEGER DEFAULT (unixepoch()),
  completed_at INTEGER,
  duration_ms INTEGER,
  steps_completed INTEGER DEFAULT 0,
  steps_total INTEGER,
  error_message TEXT,
  execution_log TEXT,
  screenshot_keys TEXT,
  FOREIGN KEY (flow_id) REFERENCES flows(id) ON DELETE CASCADE
);

API Architecture ​

Authentication Flow ​

Extension → X-Api-Key Header
    ↓
Extension API Key Middleware
    ↓
Verify Key in Database
    ↓
Set userId in Context
    ↓
Auth Middleware (Skipped)
    ↓
Route Handler

Middleware Stack ​

1. CORS Middleware ​

  • Allows browser extension origins
  • Supports chrome-extension:// and moz-extension://
  • Localhost allowed for development
  • Credentials enabled
typescript
cors({
  origin: (origin) => {
    if (origin.startsWith('chrome-extension://') ||
        origin.startsWith('moz-extension://')) {
      return origin;
    }
    return origin;
  },
  allowHeaders: ['Content-Type', 'Authorization', 'X-Api-Key'],
  credentials: true
})

2. Extension API Key Middleware ​

  • Validates X-Api-Key header
  • Checks key hash in database
  • Verifies key not revoked
  • Sets userId in context
  • Updates last_used timestamp
typescript
export async function extensionApiKeyMiddleware(c, next) {
  const header = c.req.header('X-Api-Key');
  const record = await verifyExtensionApiKey(c.env, header);

  c.set('userId', record.userId);
  c.set('userEmail', user.email);

  await next();
}

3. Auth Middleware ​

  • Skips validation if userId already set
  • Otherwise validates JWT Bearer token
  • Checks token in session_tokens table
  • Updates last_used timestamp
typescript
export async function authMiddleware(c, next) {
  // Skip if userId already set by extension API key
  const existingUserId = c.get('userId');
  if (existingUserId) {
    await next();
    return;
  }

  // Validate JWT token
  const token = authHeader.substring(7);
  const { payload } = await jose.jwtVerify(token, secret);
  c.set('userId', payload.userId);

  await next();
}

4. Rate Limiting ​

  • 100 requests per minute per user
  • Tracked in KV storage
  • Returns 429 on exceeded limits
typescript
const key = `ratelimit:${userId}:${minute}`;
const count = await c.env.KV.get(key);
if (count && parseInt(count) >= 100) {
  return c.json({ error: 'Rate limit exceeded' }, 429);
}

API Routes ​

Projects API (/api/v1/projects) ​

List Projects

typescript
GET /api/v1/projects
Headers: X-Api-Key: bgx_...

Response:
{
  "success": true,
  "data": {
    "projects": [
      {
        "id": "...",
        "name": "Example Website",
        "hostname": "example.com",
        "description": "...",
        "created_at": 1760448258,
        "updated_at": 1760448258,
        "last_accessed_at": 1760448258
      }
    ]
  }
}

Create Project

typescript
POST /api/v1/projects
Headers: X-Api-Key: bgx_...
Body: {
  "name": "Example Website",
  "hostname": "example.com",
  "description": "Test project"
}

Response:
{
  "success": true,
  "message": "Project created successfully",
  "data": {
    "project": { ... }
  }
}

Get/Create by Hostname

typescript
POST /api/v1/projects/by-hostname
Headers: X-Api-Key: bgx_...
Body: { "hostname": "github.com" }

Response:
{
  "success": true,
  "data": {
    "project": { ... },
    "created": true  // false if existing
  }
}

Test Plans API (/api/v1/test-plans) ​

Create Test Plan

typescript
POST /api/v1/test-plans
Headers: X-Api-Key: bgx_...
Body: {
  "projectId": "...",
  "name": "Login Flow Test",
  "description": "Test login functionality",
  "captureIds": ["capture1", "capture2"],
  "testCases": [
    {
      "id": "1",
      "title": "Valid login",
      "steps": ["Enter email", "Enter password", "Click submit"],
      "expected": "User logged in"
    }
  ]
}

Response:
{
  "success": true,
  "message": "Test plan created successfully",
  "data": { "plan": { ... } }
}

List Test Plans

typescript
GET /api/v1/test-plans?projectId=...
Headers: X-Api-Key: bgx_...

Response:
{
  "success": true,
  "data": {
    "plans": [{ ... }]
  }
}

Captures API (/api/v1/captures) ​

Upload Screenshot

typescript
POST /api/v1/captures/upload
Headers: X-Api-Key: bgx_...
Body: {
  "projectId": "...",
  "imageData": "data:image/png;base64,...",
  "url": "https://example.com/page",
  "title": "Login Page",
  "notes": "Test login screen",
  "domSnapshot": { ... }
}

Response:
{
  "success": true,
  "data": {
    "capture": {
      "id": "...",
      "image_r2_key": "captures/user-id/capture-id.png",
      "url": "...",
      ...
    }
  }
}

Get Capture Image

typescript
GET /api/v1/captures/:id/image
Headers: X-Api-Key: bgx_...

Response: <binary image data>
Content-Type: image/png

User Info API (/api/v1/info) ​

typescript
GET /api/v1/info
Headers: X-Api-Key: bgx_...

Response:
{
  "success": true,
  "data": {
    "key": {
      "id": "...",
      "keyPrefix": "bgx_a74a",
      "lastFour": "760c",
      "createdAt": "2025-10-14T13:23:59.000Z"
    },
    "user": {
      "id": "...",
      "email": "user@example.com",
      "name": "User Name",
      "subscriptionTier": "free",
      "apiCredits": 100,
      "createdAt": 1760448230,
      "lastLogin": null
    }
  }
}

Storage Architecture ​

R2 Screenshot Storage ​

Upload Flow:

Base64 Image → Decode → Upload to R2 → Store Key in D1

Key Format:

captures/{userId}/{captureId}-{timestamp}.png

Metadata:

typescript
{
  httpMetadata: { contentType: 'image/png' },
  customMetadata: { userId, captureId, url }
}

Local-First Sync ​

Extension → Backend:

  1. User creates project locally
  2. Local state updated immediately
  3. Background API call to backend
  4. Success: Mark as synced
  5. Failure: Retry on next sync

Backend → Extension:

  1. Background sync runs every 5 minutes
  2. Fetch all projects from backend
  3. Merge with local projects
  4. Add new projects from backend
  5. Update existing project metadata

Error Handling ​

API Errors ​

typescript
export class APIError extends Error {
  constructor(
    message: string,
    public statusCode?: number,
    public errorCode?: string
  ) {
    super(message);
  }
}

Error Responses ​

json
{
  "success": false,
  "error": "Unauthorized",
  "message": "Invalid or revoked extension API key"
}

Status Codes ​

  • 200 - Success
  • 201 - Created
  • 400 - Bad Request
  • 401 - Unauthorized
  • 403 - Forbidden
  • 404 - Not Found
  • 409 - Conflict
  • 429 - Rate Limit Exceeded
  • 500 - Internal Server Error

Deployment ​

Production ​

bash
wrangler deploy

Deploys to:

  • Worker: https://browsegenius-api.naveen-10d.workers.dev
  • Custom Domain: https://api.browsegenius.com

Secrets Management ​

bash
# Set JWT secret
wrangler secret put JWT_SECRET

# Set Resend API key (for emails)
wrangler secret put RESEND_KEY

Environment Variables ​

toml
[env.production]
vars = {
  ENVIRONMENT = "production",
  API_VERSION = "v1"
}

Performance ​

Metrics ​

  • Average Response Time: <50ms globally
  • P99 Response Time: <200ms
  • Database Query Time: <10ms
  • R2 Upload Time: <500ms (10MB file)

Caching ​

  • API responses cached at edge
  • Static assets served via CDN
  • Database queries optimized with indexes

Optimization ​

  • SQL queries use prepared statements
  • Indexes on all foreign keys
  • R2 uploads use streaming
  • Background sync batches requests

Security ​

Authentication ​

  • Extension API keys use SHA-256 hashing
  • JWT tokens signed with HS256
  • Session tokens stored in database
  • Automatic token expiration (7 days)

Authorization ​

  • All routes require authentication
  • User isolation via user_id foreign keys
  • No cross-user data access
  • API keys scoped to single user

Data Protection ​

  • HTTPS/TLS for all connections
  • API keys never logged
  • Sensitive data encrypted at rest
  • R2 objects private by default

Monitoring ​

Cloudflare Analytics ​

  • Request count and latency
  • Error rates and status codes
  • Geographic distribution
  • Cache hit rates

Custom Metrics ​

  • API key usage per user
  • Database query performance
  • R2 storage utilization
  • Rate limit violations

Logging ​

typescript
console.log(`${method} ${path} - ${status} (${duration}ms)`);

All logs available in Cloudflare Workers dashboard.

Testing ​

Local Development ​

bash
# Start local worker
wrangler dev

# Apply migrations
./scripts/migrate.sh

# Test endpoints
curl http://localhost:8787/health

Production Testing ​

bash
# Health check
curl https://api.browsegenius.com/health

# Test with API key
curl https://api.browsegenius.com/api/v1/projects \
  -H "X-Api-Key: bgx_..."

Migration ​

Database Migrations ​

bash
# Local
./scripts/migrate.sh

# Production
./scripts/migrate.sh --production

Schema Versioning ​

  • Migrations in schema/ directory
  • Numbered sequentially: 001_, 002_, etc.
  • Idempotent (safe to run multiple times)
  • Applied in order automatically

Future Enhancements ​

  • WebSocket support for real-time sync
  • GraphQL API for flexible queries
  • Bulk operations for large datasets
  • Advanced analytics and insights
  • Team collaboration features
  • Webhook integrations
  • OpenTelemetry tracing

Released under the MIT License.