A full-stack form builder with a retro operating-system soul — build forms on a Windows-95-flavored desktop, publish them, share a link + QR, and watch responses roll in.
Built for the ChaiCode "build a similar project" challenge on the exact restricted stack: Turborepo · Next.js · tRPC · Drizzle ORM · PostgreSQL · Scalar (OpenAPI).
All business logic is implemented as tRPC procedures — there are no REST endpoints for forms, submissions, views, or analytics. The only non-tRPC routes are the single tRPC HTTP handler and the OpenAPI/docs endpoints.
- Form editor / canvas — create a form, add fields dynamically, edit labels, help text, placeholders, required state, and options, reorder fields (▲/▼), duplicate, delete, pick a window theme, Save draft and Publish. Includes a live interactive preview.
- 8 field types — short text, long text, email, rating (stars), dropdown, multiple choice, checkbox, and date.
- Public form —
/forms/[slug]renders the published schema, validates required fields (client and server), stores the response as JSON, and tracks views. Fully anonymous. - Sharing — one-click public link + a QR code for any published form.
- Analytics dashboard — total views, total submissions, completion rate, average completion time, a paginated response table, and per-field breakdowns for rating / dropdown / multiple-choice / checkbox fields.
- Retro OS UI — hand-built window chrome, beveled controls, group boxes, a taskbar with a live clock, retro scrollbars, and playful microcopy. Fully responsive down to mobile.
- Scalar API docs —
/docsrenders an OpenAPI 3.1 spec that is generated by introspecting the live tRPC router (so it can't drift from the procedures). - Loading / empty / error states everywhere.
| Layer | Choice |
|---|---|
| Monorepo | Turborepo + pnpm workspaces |
| Frontend | Next.js 15 (App Router) + React 19 + TypeScript |
| API | tRPC v11 (procedures only — no REST business API) |
| Validation | Zod (shared client + server) |
| ORM | Drizzle ORM (postgres-js driver) |
| Database | PostgreSQL 16 |
| API docs | Scalar over an introspected OpenAPI 3.1 document |
| Styling | Hand-built retro CSS design system + Tailwind for responsive layout |
| Local DB | Docker Compose |
formos/
├─ apps/
│ └─ web/ # Next.js app (UI + the only HTTP routes)
│ └─ src/
│ ├─ app/
│ │ ├─ page.tsx # Dashboard (retro desktop)
│ │ ├─ editor/[id]/ # Form editor
│ │ ├─ forms/[slug]/ # Public form (anonymous)
│ │ ├─ analytics/[id]/ # Analytics dashboard
│ │ ├─ docs/route.ts # Scalar docs viewer
│ │ └─ api/
│ │ ├─ trpc/[trpc]/route.ts # THE single tRPC endpoint
│ │ └─ openapi.json/route.ts # OpenAPI spec (for Scalar)
│ ├─ components/ # dashboard, editor, runner, analytics…
│ ├─ lib/trpc/ # tRPC React client + provider
│ └─ server/api.ts # server-side tRPC caller (SSR)
├─ packages/
│ ├─ api/ # tRPC routers, context, Zod inputs, OpenAPI generator
│ ├─ db/ # Drizzle schema, client, migrations, seed, field model
│ ├─ ui/ # Retro OS component kit + retro.css
│ └─ config/ # Shared tsconfig presets
├─ docker-compose.yml # Local PostgreSQL
├─ turbo.json
└─ pnpm-workspace.yaml
React components ──(@trpc/react-query)──▶ POST/GET /api/trpc/<proc>
│
packages/api appRouter (Zod-validated)
│
packages/db Drizzle ──▶ PostgreSQL
/api/openapi.json introspects the same appRouter to produce the spec Scalar renders at /docs.
Prerequisites: Node ≥ 20, pnpm ≥ 10, and Docker (for local PostgreSQL).
# 1. Install dependencies
pnpm install
# 2. Configure environment
cp .env.example .env
# (DB defaults match docker-compose.yml. Set ADMIN_TOKEN to any passcode —
# you'll enter it once to unlock the builder/analytics.)
# 3. Start PostgreSQL
docker compose up -d
# 4. Apply the schema + load demo data
pnpm db:migrate # create the tables
pnpm db:seed # 1 demo user, 3 forms, ~78 views, ~53 submissions
# 5. Run the app
pnpm devOpen http://localhost:3000 🎉
- Dashboard → http://localhost:3000
- Example published form → http://localhost:3000/forms/chaicode-cohort-onboarding
- API docs (Scalar) → http://localhost:3000/docs
First run without seeding? No problem — a demo operator and your first form are created on demand.
| Command | What it does |
|---|---|
pnpm dev |
Run the web app in dev mode |
pnpm build |
Production build of every package |
pnpm typecheck |
Type-check all packages |
pnpm lint |
ESLint across the repo |
pnpm db:generate |
Generate a new SQL migration from the Drizzle schema |
pnpm db:migrate |
Apply migrations to the database |
pnpm db:push |
Push the schema directly (handy in dev) |
pnpm db:seed |
Reset + seed demo data |
pnpm db:studio |
Open Drizzle Studio |
demo_users— simplified auth: one demo operator owns the forms.forms—title,description, uniqueslug,status(draft|published),accent, and an orderedfieldsarray stored as JSONB.form_views— one row per public view, with an anonymoussession_id.form_submissions— answers as JSONB keyed by field id, plusduration_ms(paired to a view viasession_idfor completion-time analytics).
The field model (packages/db/src/fields.ts) is the single source of truth — the same Zod schemas validate tRPC inputs, type the JSONB column, and drive the editor/renderer.
Every operation is a tRPC procedure under form.*, submission.*, view.*, analytics.*.
Browse them at /docs (Scalar). The spec at /api/openapi.json documents how each
procedure is reached over tRPC's HTTP transport:
- Queries →
GET /api/trpc/<procedure>?input=<url-encoded JSON> - Mutations →
POST /api/trpc/<procedure>with the input as the JSON body
No duplicate REST API is generated — the doc is built by reading the real router + Zod inputs.
What's been verified in this repo:
pnpm typecheck— ✅ passes (all 5 packages)pnpm lint— ✅ passespnpm build— ✅next buildsucceedspnpm db:generate— ✅ initial migration committed inpackages/db/migrations- Runtime smoke test (no DB needed):
/,/docs, and/api/openapi.jsonall return 200, and the OpenAPI generator emits all 13 procedures.
Notes:
- Live PostgreSQL is exercised via the steps above on your machine (Docker). The repo ships the migration + seed so a clean machine reaches a working app with
migrate → seed → dev. - Auth is simplified per the brief: a single demo operator owns the forms, and the builder + analytics are gated by a shared admin passcode (
ADMIN_TOKEN, sent as thex-admin-tokenheader; enter it once on the unlock screen). Public form-filling at/forms/[slug]stays anonymous. Swap in real per-user auth later if needed. - The Scalar viewer at
/docsloads its bundle from a CDN at runtime; the OpenAPI JSON itself is served by the app.
- App: https://formos-beta.vercel.app
- Try it / feedback form (uses all 8 field types): https://formos-beta.vercel.app/forms/did-you-like-formos
- API docs (Scalar): https://formos-beta.vercel.app/docs
- Example form: https://formos-beta.vercel.app/forms/chaicode-cohort-onboarding
- Source: https://github.com/himanshu748/FormOS
- Demo video + thread on X: https://x.com/jhahimanshu653/status/2065809978703613968
Hosted on Vercel (Next.js) + Supabase (PostgreSQL via the IPv4 transaction pooler).
The builder & analytics are behind an admin passcode (ADMIN_TOKEN); public form-filling is open.
Built for the ChaiCode #ChaiForms challenge — a form builder on the restricted stack, built end-to-end (including the demo video) with Claude Opus 4.8.
apps/web is a standard Next.js app and deploys cleanly to any Node host (e.g. Vercel) with a
managed PostgreSQL DATABASE_URL, NEXT_PUBLIC_APP_URL, and ADMIN_TOKEN set. For a pnpm
monorepo, set the Vercel Root Directory to apps/web and deploy from Git (Vercel includes
the workspace automatically).
MIT — add a LICENSE file if you publish it.