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 siteAPI 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:
- Zod validation → 400 on failure
- Cloudflare Turnstile token verification → 403 on failure
- Insert row into
supabase.leads - Send confirmation email via Resend to submitter
- Send internal notification to Brett’s team
- Upsert contact + deal in HubSpot CRM
- Return
{ success: true, id: uuid }
POST /api/media-kit
Email-gated media kit download.
Pipeline:
- Validate + Turnstile check
- Insert into
supabase.media_kit_requests - Send email with signed Cloudflare R2 URL (1-hour TTL)
- 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 }| Status | Meaning |
|---|---|
400 | Validation failure |
403 | Turnstile verification failure |
409 | Duplicate submission |
429 | Rate limit exceeded |
500 | Internal server error (logged to Sentry) |
Rate Limiting
Two layers:
- Cloudflare WAF — IP-based, blocks > 10 req/min to
/api/* - Upstash Redis — sliding window, 5 form submissions per IP per hour
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',});