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 dev
Open 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.