A full-stack SaaS platform for collecting and managing user feedback. Built as a Turborepo monorepo, powered by Bun.
apps/
auth-web/ # Authentication portal (sign-in, sign-up, password reset)
admin-web/ # Customer dashboard for managing feedback
marketing/ # Public marketing website [upcoming]
feedback/ # Embeddable feedback widget for client websites [upcoming]
storybook/ # UI component catalogue
packages/
@feedback-saas/auth # Better Auth configuration, server events, email via Resend
@feedback-saas/db # Drizzle ORM schema, queries, migrations (Neon PostgreSQL)
@feedback-saas/ui # Shared React component library (coss ui built on top of Base UI and style with Tailwind v4)
@feedback-saas/utils # Shared utility functions
| Layer | Choice |
|---|---|
| Runtime / Package manager | Bun |
| Monorepo orchestration | Turborepo |
| Framework | TanStack Start + TanStack Router |
| Data fetching | TanStack Query |
| Forms | TanStack Form |
| Database | Drizzle ORM + Neon (serverless Postgres) |
| Authentication | Better Auth + Resend |
| UI components | Base UI + shadcn/ui, styled with coss ui |
| Styling | Tailwind CSS v4 |
| Icons | Tabler Icons |
| i18n | Paraglide JS |
| Error monitoring | Sentry |
| Testing | Vitest + Testing Library |
| Linting / Formatting | Oxlint + Oxfmt |
| Git hooks | Lefthook + Commitlint |
Prerequisite: Bun ≥ 1.3.14
# Install Bun if you don't have it
curl -fsSL https://bun.sh/install | bash
# Clone and install dependencies
git clone <repo-url> && cd feedback-saas
bun installCopy the environment variables template and fill in the required values:
cp .env.example .envRequired environment variables:
| Variable | Description |
|---|---|
DATABASE_URL |
Neon PostgreSQL connection string |
BETTER_AUTH_SECRET |
Random secret for session signing |
BETTER_AUTH_URL |
Auth server base URL |
BETTER_AUTH_TRUSTED_ORIGINS |
Comma-separated allowed origins |
GOOGLE_CLIENT_ID |
Google OAuth client ID |
GOOGLE_CLIENT_SECRET |
Google OAuth client secret |
RESEND_API_KEY |
Resend API key for transactional email |
VITE_FEEDBACK_SAAS_AUTH_WEB_URL |
Public URL of the auth app |
VITE_FEEDBACK_SAAS_ADMIN_WEB_URL |
Public URL of the admin app |
VITE_APP_ENV |
development | staging | production |
Optionally seed development data:
bun run seedStart all apps and watch all packages simultaneously:
bun run devStart a specific app only:
bun run dev --filter=@feedback-saas/admin-web
bun run dev --filter=@feedback-saas/auth-web
bun run dev --filter=@feedback-saas/storybook| App | Default port |
|---|---|
auth-web |
3000 |
admin-web |
3001 |
storybook |
6006 |
# Build everything
bun run build
# Run all tests
bun run test
# Lint the workspace
bun run lint
# Check formatting
bun run format
# Auto-fix formatting
bun run format:write
# Open Drizzle Studio
bun run --filter=@feedback-saas/db db:studio
# Generate a new DB migration
bun run --filter=@feedback-saas/db db:generate
# Storybook (component catalogue)
bun run dev --filter=@feedback-saas/storybookShared components live in packages/ui and are styled with coss ui, using Base UI primitives.
Install a component from the coss ui registry via the shadcn CLI, run from the repo root:
bunx --bun shadcn@latest add @coss/button -c packages/uiAll apps are hosted on Vercel and deploy automatically on every push — no manual steps needed.
| App | Branch → Environment |
|---|---|
auth-web |
main → production, develop → preview |
admin-web |
main → production, develop → preview |
marketing |
main → production, any other branch → preview |
feedback |
main → production, any other branch → preview |
storybook |
main → production, any other branch → preview |
For auth-web and admin-web, only the develop branch produces a preview deployment, with a fixed URL per app, e.g. preview.admin.feedback-saas.XXX for admin-web, preview.auth.feedback-saas.XXX for auth-web. The other apps still get a unique preview URL per branch.
Vercel automatically picks up the Turborepo config and only rebuilds apps affected by a given change.
Database migrations are not run automatically on deploy. Read more in the section Pushing schema changes to Neon.
Commits are linted against Conventional Commits via Commitlint. Lefthook runs formatting and linting on staged files before every commit.
feat: add feedback widget embed script
fix: resolve session expiry race condition
chore: bump drizzle-orm to 0.45
Run this whenever you upgrade better-auth or change its configuration in packages/auth/src/index.ts:
cd packages/db
bun x auth@latest generate --config=../auth/src/index.ts --output=./src/schemaThis regenerates packages/db/src/schema/auth-schema.ts to match the current Better Auth config. After this, follow the steps in Pushing schema changes to Neon.
Prerequisite:
packages/dbmust have a.envfile withDATABASE_URLandBETTER_AUTH_URLset.
After updating any schema file under packages/db/src/schema/, apply the changes to the database and rebuild the package:
# 1. Push schema changes to Neon
cd packages/db && bun run db:push
# 2. Rebuild the db package so the updated schema is picked up at runtime
bun run --filter=@feedback-saas/db buildThen restart the dev server.
db:pushvsdb:generate: These are two separate Drizzle workflows — do not mix them.
db:pushdiffs the schema directly against the DB and applies changes. No migration files needed or used.db:generate+db:migrategenerates SQL migration files and runs them — use this for production deployments where migration history matters.For day-to-day development,
db:pushalone is sufficient.