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 HandlerMiddleware Stack
1. CORS Middleware
- Allows browser extension origins
- Supports
chrome-extension://andmoz-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-Keyheader - Checks key hash in database
- Verifies key not revoked
- Sets
userIdin context - Updates
last_usedtimestamp
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
userIdalready set - Otherwise validates JWT Bearer token
- Checks token in
session_tokenstable - Updates
last_usedtimestamp
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/pngUser 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 D1Key Format:
captures/{userId}/{captureId}-{timestamp}.pngMetadata:
typescript
{
httpMetadata: { contentType: 'image/png' },
customMetadata: { userId, captureId, url }
}Local-First Sync
Extension → Backend:
- User creates project locally
- Local state updated immediately
- Background API call to backend
- Success: Mark as synced
- Failure: Retry on next sync
Backend → Extension:
- Background sync runs every 5 minutes
- Fetch all projects from backend
- Merge with local projects
- Add new projects from backend
- 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- Success201- Created400- Bad Request401- Unauthorized403- Forbidden404- Not Found409- Conflict429- Rate Limit Exceeded500- Internal Server Error
Deployment
Production
bash
wrangler deployDeploys 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_KEYEnvironment 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_idforeign 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/healthProduction 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 --productionSchema 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