A Turborepo/Bun monorepo for Alexa Lashes, a lash studio business: a public marketing site and an internal admin app, backed by a shared Postgres (Neon) database via Drizzle ORM and Better Auth.
apps/marketing— public site. React 19 + TanStack Start (file-based routing, SSR) + Vite + Tailwind CSS v4, i18n via Paraglide JS (skbase locale,en/ruprefixed), deployed to Netlify. Reviews are fetched from the DB at build time (bun run generate-reviews) and baked into a static JSON file — not fetched per request.apps/admin— internal admin dashboard (SPA). Same stack, Google OAuth via Better Auth, gated by an allow-list (allowed_emailtable) — no self-service signup. Currently manages customer reviews (list per locale, create/edit/delete, enable/disable, drag-and-drop reordering) and has a "Deploy marketing site" button to trigger a Netlify rebuild, since marketing's reviews are static at build time (see above). Deployed to Netlify.
packages/db— Drizzle schema (reviews,review_translations,allowed_email, Better Auth's own tables), query functions, and the shareddbclient (drizzle-orm/neon-httpagainst Neon serverless Postgres).packages/auth— shared Better Auth instance/config, server helpers (getSession,ensureSession) and client helpers (signIn,signOut, ...) for the admin app.packages/ui— shared React components: custom components, shadcn/ui wrappers (built on Base UI), Lucide icons, and the single Tailwind entry (styles.css) both apps import.packages/contracts— hand-written Zod schemas for API/DB input shapes (e.g.createReviewSchema/updateReviewSchema) and their inferred TypeScript types — the single source of truth for validation, shared bypackages/db's query layer and the admin app's server functions.packages/types— small, dependency-free shared primitives (currentlylocales/baseLocale/Locale) used across packages without creating circular package dependencies.
- Package manager: Bun (
bun@1.3.14, pinned viamise.toml) - Monorepo orchestration: Turborepo (
turbo.json) - Framework: TanStack Start (file-based router + SSR) on React 19 and Vite, deployed to Netlify
- Data: Drizzle ORM + Neon serverless Postgres
- Auth: Better Auth (Google OAuth)
- Validation: Zod
- Forms: TanStack Form
- Data fetching: TanStack Query
- Styling: Tailwind CSS v4 + shadcn/ui on Base UI
- i18n (marketing only): Paraglide JS
- Lint/format: oxlint / oxfmt
- Git hooks: lefthook + commitlint
There is no test suite/framework configured in this repo (no vitest/jest/playwright).
Requires Bun 1.3.14 (see mise.toml if you use mise).
bun installCreate a .env file at the repo root (see Environment variables below),
then:
bun run dev # runs all apps in dev mode (marketing + admin on port 3000)
bun run build # builds all apps/packages, respecting the dependency graphScope a command to a single app/package with Turborepo's --filter:
bun run dev --filter=alexa-lashes/admin
bun run build --filter=alexa-lashes/adminOther root scripts:
bun run lint # oxlint
bun run format # oxfmt --check
bun run format:write # oxfmt (fixes in place)
bun run clean # remove build outputs, node_modules, and the lockfile everywherepackages/db has its own scripts (run from packages/db, needs packages/db/.env):
bun run db:generate # generate a Drizzle migration from schema changes
bun run db:push # push schema directly to the DB
bun run db:studio # open Drizzle Studio
bun run better-auth:generate # regenerate auth-schema.ts from packages/auth's config
bun run seed:email # seed the allowed_email table
bun run seed:review # seed a sample reviewRoot .env (loaded via bun --env-file=../../.env) and packages/db/.env (for Drizzle CLI
commands run from that package):
DATABASE_URLBETTER_AUTH_URLBETTER_AUTH_SECRETGOOGLE_CLIENT_IDGOOGLE_CLIENT_SECRETEMAIL_ADDRESSEMAIL_PASSWORDVITE_GOOGLE_MAPS_API_KEYVITE_GOOGLE_ANALYTICS_API_KEYNETLIFY_MARKETING_BUILD_HOOK_URL— a Netlify build hook URL for theapps/marketingsite (site settings → Build hooks there).apps/marketingfetches its reviews from the DB at build time, not at request time, so adding/editing a review in the admin app doesn't go live until this is triggered. The admin dashboard has a "Deploy marketing site" button that posts to it. For production, set this var on the admin Netlify site (not the marketing one).
- Branch names:
<type>/<short-description>(e.g.fix/dashboard), same types as commits (below). - Deployment is fully automatic via Netlify, no manual/CI deploy step:
- Pushing to any remote branch triggers a Netlify branch deploy (preview) for both apps.
- Merging a PR into
maintriggers a Netlify production deploy for both apps immediately.
.github/workflows/codeql.ymlruns CodeQL security analysis on push/PR tomainand weekly. There is no CI workflow for build/lint/test — those run locally via git hooks.
Enforced by commitlint (lefthook commit-msg hook) — Conventional Commits,
restricted to: build, chore, ci, docs, feat, fix, perf, refactor, revert, style,
test. Example: feat: added new feature [TICKET-ID].
pre-commit runs oxfmt --fix and oxlint --fix against staged files automatically.
See CLAUDE.md for a deeper architecture walkthrough (server-function/query layer conventions,
auth flow, i18n locale strategy, and coding conventions).
This repo also uses Claude Code skills for common workflows
(.claude/skills/) — check that directory for the current list, as it grows over time.