Architecture
Bondery is a monorepo managed with Turborepo and npm workspaces. Apps and shared packages share tooling, types, and translations; Turborepo orchestrates dev, build, lint, and type-check across the tree.
usebondery/bondery
Mental model
| Layer | Role |
|---|---|
| Postgres | Source of truth for all user data (packages/db, Prisma) |
| Fastify API | Better Auth, business logic, list/search endpoints, imports, mobile sync (push/pull/bootstrap) |
| Clients | Webapp, mobile, and Chrome extension — OAuth sessions via Better Auth on the API; app data via the API |
Better Auth runs on the API (/auth/*). Clients obtain session cookies or bearer tokens and attach them to API requests. The API uses Prisma (@bondery/db) to read and write Postgres — authorization is enforced in application code, not database RLS.
Domain mutations (create/update/delete contacts, groups, tags, etc.) live in apps/api/src/domains/. REST routes and POST /api/sync/push call the same functions so web, mobile, and future clients stay consistent.
Repository structure
Apps
apps/webapp — Web application
Source: apps/webapp · Local dev: port 26632 · http://localhost:26632
The primary user-facing product at app.usebondery.com.
| Concern | Technology |
|---|---|
| Framework | Next.js 16 (App Router, React Server Components) |
| Language | TypeScript |
| UI | Mantine v9 via @bondery/mantine-next |
| Supplemental styles | Tailwind CSS |
| Data fetching | TanStack Query + lib/query/ fetchers and hooks |
| API transport | clientApi* / serverApi* wrappers in lib/api/client.ts |
| Authentication | Better Auth on the API — webapp uses OAuth 2.1 + PKCE against the API issuer; session cookie encrypted by the webapp |
| Rich text | Tiptap |
| Localization | i18next + react-i18next via @bondery/translations (useWebTranslations) |
| Icons | Tabler Icons |
Server components and client hooks call the Fastify API with the user's session token. A small set of Next.js route handlers (e.g. streaming chat) proxy to the API after verifying the session locally.
apps/api — REST API server
Source: apps/api · Local dev: port 26631 · http://localhost:26631 · Production: api.usebondery.com
Mobile sync WebSocket: ws://localhost:26631/api/sync/ws (same port as the API).
Central backend for all product data and privileged operations.
| Concern | Technology |
|---|---|
| Framework | Fastify v5 |
| Language | TypeScript |
| Database | Postgres via Prisma (@bondery/db) |
| Domain layer | src/domains/* — shared by REST routes and sync push |
| Sync | Changelog pull/bootstrap (GET /api/sync/pull, GET /api/sync/bootstrap), mutation push (POST /api/sync/push) |
| Nodemailer + React Email templates | |
| API contract | Zod schemas in @bondery/schemas → generated packages/openapi-spec/openapi.yaml → build-time Fumadocs OpenAPI MDX |
API documentation pipeline
CI enforces: every public route has description + response; openapi.yaml is up to date; Redocly lint passes. See API routes.
Every authenticated request validates the Better Auth session or API key. List endpoints follow a shared pagination contract (limit, offset, search, sort, nested pagination object).
apps/mobile — Mobile application
Source: apps/mobile · Local dev (Metro): port 26634 · http://localhost:26634
Native iOS and Android client built with Expo. Offline-capable via local SQLite and server-authoritative pull sync.
| Concern | Technology |
|---|---|
| Framework | Expo + Expo Router |
| Language | TypeScript |
| UI | Tamagui |
| Local database | expo-sqlite |
| Read sync | GET /api/sync/bootstrap + GET /api/sync/pull (batched sync_change_log) |
| Write sync | REST when online; pending_mutations outbox → POST /api/sync/push when offline |
| Authentication | Better Auth (OAuth via API) |
| Localization | i18next via @bondery/translations (useMobileTranslations) |
See Local development setup for running the sync stack locally.
apps/website — Marketing site
Source: apps/website · Local dev: port 26630 · http://localhost:26630
Public landing page at usebondery.com.
| Concern | Technology |
|---|---|
| Framework | Next.js (App Router) |
| UI | Mantine v9 |
| Localization | i18next via @bondery/translations |
apps/chrome-extension — Browser extension
Source: apps/chrome-extension · Local dev (WXT HMR): port 26633 · http://localhost:26633
Saves contacts from social networks while browsing.
| Concern | Technology |
|---|---|
| Extension framework | WXT |
| UI | React + Mantine v9 |
| Authentication | Better Auth OAuth via chrome.identity (API as authorization server) |
| Data | Fastify API (contact create/update, redirect endpoint) |
| Supported sites | LinkedIn, Facebook, Instagram |
packages/db — Database
Source: packages/db
PostgreSQL schema, Prisma migrations, and generated client shared by the API.
| Concern | Technology |
|---|---|
| Database | PostgreSQL (PostGIS in self-host compose) |
| ORM | Prisma |
| Migrations | prisma migrate in packages/db/prisma/migrations/ |
| Raw SQL | pnpm run db:functions for Postgres functions |
Local dev: Postgres on port 54322 via deploy/bondery/docker-compose.dev-db.yml, then:
pnpm run db:migrate:devAfter schema changes, commit the new migration files and regenerate the Prisma client (pnpm run db:generate runs on install).
Fun fact: Bondery local apps use consecutive ports in the 2663x block — phone keypad B-O-N-D → 2-6-6-3 ("Dial BOND"). Local Postgres uses 54322 (direct connection, no gateway). Local API Redis uses 26636 (pnpm run start:redis). Port constants live in packages/schemas/src/constants/dev-ports.ts.
Shared packages
All compilable packages extend @bondery/typescript-config (NodeNext, declaration + declarationMap). Internal imports use #* subpath imports with .js extensions; external consumers use package.json exports (default → dist/).
| Package | Role |
|---|---|
packages/typescript-config | Shared base.json, react-library.json, nextjs.json tsconfigs |
packages/db | Prisma schema, migrations, generated client |
packages/schemas | Zod schemas, types, sync protocol, locale catalog |
packages/translations | i18next namespaces and JSON locale files (en, cs, de) |
packages/helpers | Shared utilities, API_ROUTES, global URLs |
packages/emails | React Email templates (preview for local dev, dev for tsc --watch) |
packages/mantine-next | Reusable Mantine components for Next.js apps |
packages/branding | Logos, icon assets, React icon components (@bondery/branding/react) |
packages/vcard | vCard generation for API export and share flows |
Build: rimraf dist && tsc per package. Exports: run pnpm run sync-exports after adding public subpaths. Go-to-definition: declarationMap maps from dist/*.d.ts back to src/.
packages/schemas
Single source of truth for Zod schemas, inferred TypeScript types, and the mobile sync protocol (src/sync/). Import domain types from here — do not redefine them in apps. Database row shapes come from @bondery/db (Prisma).
Supported UI locales are declared in packages/schemas/locale/supported-locales.json and exported as SUPPORTED_LOCALES, DEFAULT_LOCALE, and APP_LOCALE_METADATA from @bondery/schemas/locale.
packages/translations
User-facing copy for webapp, website, mobile, and the Chrome extension. Built on i18next with react-i18next (web) / expo-localization + i18next (mobile). Namespace layout and preload groups live in manifest.json; CI runs i18next-cli for types and lint, and custom scanners (check:i18n:*) for structure, used keys, and locale parity.
Supported locales (source of truth: supported-locales.json above — add every new key under src/locales/{locale}/ for each):
| Code | Language |
|---|---|
en | English (reference / fallback) |
cs | Czech |
de | German |
In application code, import locale constants from the schemas package (translations re-exports them for convenience):
import { DEFAULT_LOCALE, SUPPORTED_LOCALES } from "@bondery/schemas/locale";
import { i18nConfig, resourceLoader } from "@bondery/translations";Markdown docs cannot import TypeScript modules; keep supported-locales.json in sync when adding a locale and update this table in the same change.
See also packages/translations/README.md.
packages/helpers
Shared pure utilities, API_ROUTES, global URL constants, and platform helpers used across apps.
packages/emails
React Email templates rendered and sent by the API.
Local dev: port 26639 · pnpm run dev:emails → http://localhost:26639. Runs alongside the Chrome extension dev server (26633) without a port conflict.
packages/mantine-next
Reusable Mantine-based components (avatars, data tables, modals) shared by webapp and website.
packages/branding
Logos, icon assets, and React icon components.
packages/vcard
vCard generation used by API export and share flows.
Data flow
┌─────────────┐ session ┌─────────────┐ Prisma ┌──────────────┐
│ Clients │ ──────────▶ │ apps/api │ ────────────▶ │ Postgres │
│ web / mobile│ ◀────────── │ (Fastify) │ ◀──────────── │ │
│ extension │ JSON └──────┬──────┘ └──────┬───────┘
└──────┬──────┘ │ │
│ │ pull / bootstrap │ changelog
│ Better Auth (API) │ │
│ (login only) ▼ ▼
└──────────────────▶ mobile SQLite ◀── sync_change_log ──┘Webapp and extension: Better Auth on the API for login; all contact, group, tag, interaction, and settings data through the API.
Mobile: Better Auth for login; bootstrap + pull for reads; mutation outbox → sync push for tier-1 writes. Server state wins on pull; optimistic is_pending is UI-only until push completes.
API internals: Route handlers are thin. domains/* functions accept a DomainContext (user, Prisma client) and return { data, txid } where sync confirmation is required.
Sync (mobile)
The API validates the session and serves per-user changelog batches from sync_change_log.
| Path | Purpose |
|---|---|
GET /api/sync/bootstrap | Initial SQLite snapshot |
GET /api/sync/pull | Incremental batched changes |
POST /api/sync/push | Apply offline mutation outbox |
Protocol version headers (X-Bondery-Sync-Protocol, X-Bondery-SQLite-Schema) gate compatibility. Full design: Sync architecture (mobile).