Skip to content
← All docs

Architecture

The LeadFella ecosystem is two independent systems that share a brand but not a codebase or a database.

                          ┌───────────────────────────────────────────┐
                          │                 Visitors                   │
                          └───────────────────┬─────────────────────────┘
                                              │ HTTPS
            ┌─────────────────────────────────┼─────────────────────────────────┐
            │                                 │                                 │
  leadfella.com                      api.leadfella.com                  app.leadfella.com
 ┌──────────────────┐              ┌────────────────────┐            ┌──────────────────────┐
 │  Marketing site  │   /api/*     │  Website API       │            │  Product app (SPA)   │
 │  Next.js 15      │─────────────▶│  Express + TS      │            │  React + Vite        │
 │  (App Router)    │              │                    │            │        │ /api         │
 │  + /admin panel  │              │  waitlist/contact/ │            │        ▼              │
 └──────────────────┘              │  admin             │            │  Spring Boot monolith│
                                   └─────────┬──────────┘            │  (lead, scraper,     │
                                             │                       │   intelligence, ...) │
                                             ▼                       │   + Playwright-Java  │
                                       ┌───────────┐                 └──────────┬───────────┘
                                       │ PostgreSQL│                            │
                                       │ (website) │                            ▼
                                       └───────────┘                      ┌───────────┐
                                                                          │  MongoDB  │
                                                                          │ (product) │
                                                                          └───────────┘

Two systems

Product app (the SPA you sell)

  • Frontend: React + Vite + TypeScript + Tailwind + shadcn/ui, a pure SPA.
  • Backend: a single Java Spring Boot modular monolith (Java 21), packaged by feature: lead, scraper, intelligence, scoring, crm, outreach. It does everything, including Google Maps scraping via Playwright-Java in-process.
  • Database: MongoDB, owned entirely by the backend.
  • Served: the built SPA is bundled into the backend's static resources and runs as a single jar on a single port. In production at app.leadfella.com.
  • Lives in ../../apps/web and ../../backend. Not part of this website project.

Marketing website (this project)

  • Frontend (../frontend): Next.js 15 (App Router) + React 19 + TypeScript + Tailwind + shadcn/ui + Framer Motion. Statically pre-rendered marketing pages plus an /admin area (client-rendered, noindex). Served at leadfella.com.
  • Backend (../backend): a small Express + TypeScript API for the waitlist, contact form, and admin panel. Served at api.leadfella.com.
  • Database: its own PostgreSQL instance (tables waitlist_leads, contact_messages, schema_migrations). Completely separate from the product's MongoDB.

Why decouple the website

The product app is a Vite SPA served by Spring Boot. The marketing site has different needs - SEO, static rendering, fast first paint, and a tiny lead-capture API - so it is intentionally a separate Next.js frontend plus a small Express API that can be deployed and scaled on their own.

Request flow

  • Marketing pages are static; the browser loads them directly from the Next.js deployment.
  • Forms (early access, contact) POST to the Express API over /api. The frontend talks to the API through a typed client (src/lib/api.ts) and reads the API base from NEXT_PUBLIC_API_URL.
  • Admin panel (/admin) authenticates against the Express API (POST /api/admin/login → JWT), stores the token in localStorage, and calls the admin endpoints with a Bearer token. A 401 clears the token and signs the operator out.

Boundaries (hard rules)

  • The website never touches the product's MongoDB, and the product never touches the website's PostgreSQL.
  • The website frontend talks only to the website API over /api - no direct database access from the browser.
  • Website users (early-access leads, the single admin) are not product users. The two auth systems are independent.

Website backend internals

backend/src/
  config/      env (zod-validated) + logger (pino)
  db/          pool, migration runner, repositories
  middleware/  error handling, rate limiting
  models/      row/DTO types + zod input schemas
  auth/        admin JWT + requireAdmin middleware
  routes/      health, waitlist, contact, admin
  lib/         shared helpers (CSV)
  scripts/     migrate, seed:admin, maintenance CLIs

Controllers stay thin, logic lives in services/repositories, and persistence is parameterized SQL through a single pg pool. See configuration.md and api.md for specifics.