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.