A self-hosted form management platform in the spirit of Google Forms, Typeform, and Jotform — drag-and-drop form building, role-based access control, response management, analytics, and webhooks. Built as a full-stack TypeScript project on Express and React.
| Layer | Tech |
|---|---|
| Backend | Node.js, Express 5, TypeScript, Prisma ORM, PostgreSQL |
| Auth | JWT access tokens + rotating refresh tokens (httpOnly cookie) |
| Frontend | React, TypeScript, Vite, Tailwind CSS, shadcn/ui, Zustand |
| Forms | React Hook Form + zod validation (shared approach on both ends) |
| Storage | Local disk (uploads/) |
See IMPLEMENTATION_PLAN.md for the full design and
docs/API.md for the API reference (auth, /api/v1, webhooks, env vars).
- Phase 1 – Foundation: auth, user management, roles (Root / Staff / User), database setup, project structure
- Phase 2 – Forms: drag-and-drop form builder, dynamic renderer, validation engine, versioned publishing, public form pages
- Phase 3 – Responses: submission storage with metadata, response dashboard (search/sort/pagination), detail view, single/bulk delete, CSV/Excel/JSON/PDF exports
- Phase 4 – Permissions: per-form staff permissions (granular keys + presets), form assignment/sharing, access-control middleware, permission-scoped dashboard
- Phase 5 – Analytics: form-view tracking, conversion, submission trends, completion time, device/geo breakdowns, field-by-field analytics, and a cross-form overview dashboard
- Phase 6 – Integrations: outbound webhooks (HMAC-signed, retries, delivery logs), in-app + email notifications, and a read-only external API authenticated by API keys
- Phase 7 – Production readiness: audit logs, builder autosave + version-history restore, health/readiness + request metrics,
pg_dumpbackups, and API docs
- Node.js 22+
- PostgreSQL 16+ (on Windows:
winget install PostgreSQL.PostgreSQL.17)
In psql (as the postgres superuser):
CREATE ROLE expressforms LOGIN PASSWORD 'your-password' CREATEDB;
CREATE DATABASE expressforms OWNER expressforms;CREATEDB lets Prisma create its temporary shadow database during migrations.
cd backend
npm install
copy .env.example .env # cp on macOS/LinuxEdit backend/.env: set DATABASE_URL to your connection string, a long random
JWT_ACCESS_SECRET (openssl rand -hex 32), and the ROOT_EMAIL / ROOT_PASSWORD
for the root account. Then:
npx prisma generate # generate the Prisma client (src/generated/prisma)
npx prisma migrate dev # apply migrations
npx prisma db seed # create the root accountcd backend && npm run dev # API on http://localhost:4000cd frontend && npm install
npm run dev # app on http://localhost:5173 (proxies /api to :4000)Sign in at http://localhost:5173/login with your ROOT_EMAIL / ROOT_PASSWORD.
PowerShell:
Invoke-RestMethod http://localhost:4000/api/health
Invoke-RestMethod -Method Post -ContentType 'application/json' `
-Uri http://localhost:4000/api/auth/login `
-Body '{"email":"<ROOT_EMAIL>","password":"<ROOT_PASSWORD>"}'curl:
curl http://localhost:4000/api/health
curl -X POST http://localhost:4000/api/auth/login \
-H 'content-type: application/json' \
-d '{"email":"<ROOT_EMAIL>","password":"<ROOT_PASSWORD>"}'ERR_MODULE_NOT_FOUND: Cannot find module '.../src/generated/prisma/client.js' — the
Prisma client is a generated artifact that lives in backend/src/generated/prisma/ and is not
committed to git, so it is absent after a fresh clone, a node_modules reinstall, or git clean.
Regenerate it:
cd backend
npx prisma generatebackend/ Express 5 + Prisma API server
frontend/ Vite + React SPA