Skip to content

Backend Architecture

import { Aside } from ‘@astrojs/starlight/components’;

Overview

The backend is entirely serverless, running as Next.js API Routes and Edge Functions deployed on Vercel. There is no dedicated backend server or container to manage.

apps/api/ → api.brettjohnson.xyz (standalone API app)
apps/www/app/api/ → Co-located API routes for the main site

API Surface

POST /api/booking

Handles inquiry form submissions.

Request body (validated via Zod):

{
name: string; // required, max 100 chars
organization: string; // required, max 150 chars
email: string; // required, valid email
engagementType: 'keynote' | 'workshop' | 'training' | 'consulting' | 'media' | 'other';
budgetRange: '<10k' | '10k-25k' | '25k-50k' | '50k-75k' | '75k+';
eventDate?: string; // ISO 8601, optional
message: string; // required, max 2000 chars
}

Pipeline:

  1. Zod validation → 400 on failure
  2. Cloudflare Turnstile token verification → 403 on failure
  3. Insert row into supabase.leads
  4. Send confirmation email via Resend to submitter
  5. Send internal notification to Brett’s team
  6. Upsert contact + deal in HubSpot CRM
  7. Return { success: true, id: uuid }

POST /api/media-kit

Email-gated media kit download.

Pipeline:

  1. Validate + Turnstile check
  2. Insert into supabase.media_kit_requests
  3. Send email with signed Cloudflare R2 URL (1-hour TTL)
  4. Return { success: true }

GET /api/status

Health check endpoint — returns build metadata and milestone progress.

{
status: 'ok';
version: string;
environment: 'development' | 'staging' | 'production';
milestones: Array<{ id: string; name: string; progress: number }>;
services: Record<string, 'up' | 'degraded' | 'down'>;
}

External Integrations

Supabase (PostgreSQL)

  • Client initialized in lib/supabase.ts
  • Service role key used server-side only (never exposed to browser)
  • Row-level security enabled on all tables
  • See Database Architecture for schema

Resend (Transactional Email)

  • Templates built with React Email (packages/email-templates/)
  • Two templates: BookingConfirmation, InternalLeadNotification
  • From address: hello@brettjohnson.xyz

HubSpot CRM

  • Creates/upserts Contact + Deal on booking submission
  • Deal pipeline: New Inquiry → Qualified → Proposal → Booked → Closed Won
  • Env var: HUBSPOT_PRIVATE_APP_TOKEN

Cloudflare Turnstile

  • Bot challenge on all form submissions
  • Server-side token verification
  • Env vars: TURNSTILE_SECRET_KEY (server), NEXT_PUBLIC_TURNSTILE_SITE_KEY (client)

Error Handling

All API routes return a consistent shape:

// Success
{ success: true, data?: unknown }
// Error
{ success: false, error: string, code?: string }
StatusMeaning
400Validation failure
403Turnstile verification failure
409Duplicate submission
429Rate limit exceeded
500Internal server error (logged to Sentry)

Rate Limiting

Two layers:

  1. Cloudflare WAF — IP-based, blocks > 10 req/min to /api/*
  2. Upstash Redis — sliding window, 5 form submissions per IP per hour
lib/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
export const bookingRateLimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(5, '1 h'),
prefix: 'rl:booking',
});