Skip to content

Latest commit

ย 

History

12 Commits

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐Ÿ“‘ Formly โ€“ A Modern Full-Stack Form Management Platform

Next.js FastAPI TypeScript Python SQLite TailwindCSS Vercel Render

A production-inspired full-stack web application demonstrating modern software engineering practices through a scalable client-server architecture, responsive user interface, RESTful APIs, and cloud deployment.


๐Ÿ“š Table of Contents


๐ŸŒ Live Demo


โœจ Features

Category Highlights
๐Ÿ“‹ Dashboard Create, duplicate, delete, and manage forms โ€ข Search and sort forms โ€ข Responsive dashboard interface
๐Ÿ› ๏ธ Form Builder Drag-and-drop question reordering โ€ข Multiple question types โ€ข Required field support โ€ข Question descriptions โ€ข Live preview โ€ข Publish/Unpublish forms
๐Ÿ‘ค Respondent Experience One-question-at-a-time interface โ€ข Progress indicator โ€ข Input validation โ€ข Shareable public links
๐Ÿ“Š Responses & Analytics View submitted responses โ€ข Per-question statistics โ€ข Rating summaries โ€ข Choice distributions
โš™๏ธ Backend RESTful FastAPI API โ€ข SQLAlchemy ORM โ€ข SQLite persistence โ€ข Automatic seed data

๐Ÿ› ๏ธ Tech Stack

Frontend: Next.js 15 (App Router) ยท TypeScript ยท TailwindCSS ยท Framer Motion ยท React Hook Form + Zod ยท Axios ยท dnd-kit

Backend: Python ยท FastAPI ยท SQLAlchemy ยท Pydantic ยท SQLite

Deployment: Frontend โ†’ Vercel ยท Backend โ†’ Render ยท Database โ†’ SQLite


โฌ‡๏ธ Architecture Overview

The app is a classic client/server split:

  • The Next.js frontend is a pure client of the API โ€” it never talks to the database directly. Every page fetches through services/*.ts (thin Axios wrappers) into typed TypeScript interfaces in types/*.ts.
  • The FastAPI backend exposes a REST API. Each router handles HTTP concerns only; anything resembling business logic (duplicating a form, computing statistics) lives in app/services/, keeping routers thin and readable.
  • The respondent flow requires no authentication โ€” publishing a form generates a random share_slug used as its public URL. A separate /api/public/forms/{share_slug} endpoint only returns forms that are actually published, so drafts are never exposed.
  • Creator authentication is intentionally out of scope for this assignment (per the spec) โ€” the app assumes a single default creator.
Browser โ”€โ”€ Next.js (Vercel) โ”€โ”€ Axios/HTTP โ”€โ”€ FastAPI (Render) โ”€โ”€ SQLAlchemy โ”€โ”€ SQLite

๐Ÿ“‚ Folder Structure

typeform-clone/
โ”œโ”€โ”€ frontend/
โ”‚   โ”œโ”€โ”€ app/                  # Next.js App Router pages
โ”‚   โ”‚   โ”œโ”€โ”€ dashboard/
โ”‚   โ”‚   โ”œโ”€โ”€ builder/[formId]/
โ”‚   โ”‚   โ”œโ”€โ”€ form/[formId]/        # public respondent flow (formId = share_slug)
โ”‚   โ”‚   โ””โ”€โ”€ responses/[formId]/
โ”‚   โ”œโ”€โ”€ components/
โ”‚   โ”‚   โ”œโ”€โ”€ ui/                # Button, Input, Badge, Modal, Toggle
โ”‚   โ”‚   โ”œโ”€โ”€ builder/            # question editor, sidebar, live preview
โ”‚   โ”‚   โ”œโ”€โ”€ dashboard/          # form cards, create/delete modals
โ”‚   โ”‚   โ”œโ”€โ”€ public-form/        # question screen, progress bar, thank-you
โ”‚   โ”‚   โ””โ”€โ”€ responses/          # stat cards, response table/detail
โ”‚   โ”œโ”€โ”€ hooks/                  # useForm, useKeyboardNav
โ”‚   โ”œโ”€โ”€ services/                # api.ts + typed calls per resource
โ”‚   โ”œโ”€โ”€ lib/                     # utils.ts (cn), validation.ts (Zod)
โ”‚   โ”œโ”€โ”€ types/                   # shared TS interfaces mirroring backend schemas
โ”‚   โ””โ”€โ”€ styles/                  # Tailwind globals + theme tokens
โ”‚
โ”œโ”€โ”€ backend/
โ”‚   โ”œโ”€โ”€ app/
โ”‚   โ”‚   โ”œโ”€โ”€ routers/    # forms.py, questions.py, responses.py, stats.py
โ”‚   โ”‚   โ”œโ”€โ”€ models/     # SQLAlchemy: Form, Question, QuestionOption, Response, Answer
โ”‚   โ”‚   โ”œโ”€โ”€ schemas/    # Pydantic request/response shapes
โ”‚   โ”‚   โ”œโ”€โ”€ services/   # form_service.py, stats_service.py
โ”‚   โ”‚   โ”œโ”€โ”€ database/   # engine/session setup
โ”‚   โ”‚   โ””โ”€โ”€ main.py     # app instance, CORS, router registration
โ”‚   โ”œโ”€โ”€ seed.py          # seeds 2 published forms + ~21 responses
โ”‚   โ””โ”€โ”€ requirements.txt
โ”‚
โ””โ”€โ”€ README.md

๐Ÿ“Š Database Schema

Forms
โ”œโ”€โ”€ id            INTEGER PK
โ”œโ”€โ”€ title         TEXT NOT NULL
โ”œโ”€โ”€ description   TEXT NULL
โ”œโ”€โ”€ status        TEXT NOT NULL      -- "draft" | "published"
โ”œโ”€โ”€ share_slug    TEXT UNIQUE        -- public URL id, set on publish
โ”œโ”€โ”€ created_at    DATETIME
โ””โ”€โ”€ updated_at    DATETIME

Questions
โ”œโ”€โ”€ id            INTEGER PK
โ”œโ”€โ”€ form_id       INTEGER FK -> Forms.id (CASCADE DELETE)
โ”œโ”€โ”€ type          TEXT NOT NULL      -- short_text|long_text|multiple_choice|
โ”‚                                       dropdown|email|number|yes_no|rating
โ”œโ”€โ”€ title         TEXT NOT NULL
โ”œโ”€โ”€ description   TEXT NULL          -- help text
โ”œโ”€โ”€ placeholder   TEXT NULL
โ”œโ”€โ”€ is_required   BOOLEAN DEFAULT FALSE
โ”œโ”€โ”€ order_index   INTEGER NOT NULL   -- drives drag-and-drop order
โ””โ”€โ”€ rating_max    INTEGER NULL       -- only for type = rating

QuestionOptions                       -- for multiple_choice / dropdown
โ”œโ”€โ”€ id            INTEGER PK
โ”œโ”€โ”€ question_id   INTEGER FK -> Questions.id (CASCADE DELETE)
โ”œโ”€โ”€ label         TEXT NOT NULL
โ””โ”€โ”€ order_index   INTEGER NOT NULL

Responses                             -- one row per submission
โ”œโ”€โ”€ id            INTEGER PK
โ”œโ”€โ”€ form_id       INTEGER FK -> Forms.id (CASCADE DELETE)
โ”œโ”€โ”€ submitted_at  DATETIME
โ””โ”€โ”€ is_complete   BOOLEAN DEFAULT TRUE

Answers                               -- one row per question per response
โ”œโ”€โ”€ id            INTEGER PK
โ”œโ”€โ”€ response_id   INTEGER FK -> Responses.id (CASCADE DELETE)
โ”œโ”€โ”€ question_id   INTEGER FK -> Questions.id
โ””โ”€โ”€ value         TEXT NOT NULL       -- stored as text for every question type

๐Ÿงท ER Diagram

Forms 1 โ”€โ”€โ”€โ”€ * Questions 1 โ”€โ”€โ”€โ”€ * QuestionOptions
  โ”‚
  โ””โ”€โ”€โ”€โ”€ * Responses 1 โ”€โ”€โ”€โ”€ * Answers โ”€โ”€โ”€โ”€ * Questions

Design notes

  • Answers.value is TEXT for every question type (numbers and choices included) instead of a nullable column per type. This keeps one simple, normalized table instead of column sprawl, and is trivial to parse back into the right type in the stats layer (stats_service.py).
  • Cascade deletes are used throughout (ondelete="CASCADE" + SQLAlchemy cascade="all, delete-orphan"), so deleting a form cleanly removes its questions, options, responses, and answers.
  • order_index on both Questions and QuestionOptions is what powers drag-and-drop reordering in the builder; it's re-numbered contiguously whenever a question is deleted.

๐Ÿ“ƒ API Documentation

All endpoints are prefixed as shown. FastAPI also serves interactive docs at /docs (Swagger UI) once the backend is running.

Forms

Method Path Description
GET /api/forms List all forms (id, title, status, response_count)
POST /api/forms Create a form
GET /api/forms/{id} Get a form with its ordered questions
PATCH /api/forms/{id} Rename / update description
DELETE /api/forms/{id} Delete a form (cascades)
POST /api/forms/{id}/duplicate Duplicate a form + its questions
POST /api/forms/{id}/publish Publish, generating a share_slug
POST /api/forms/{id}/unpublish Revert to draft
GET /api/public/forms/{share_slug} Public, no auth โ€” used by the respondent flow

Questions

Method Path Description
POST /api/forms/{formId}/questions Create a question
PATCH /api/questions/{id} Update a question
DELETE /api/questions/{id} Delete a question
POST /api/forms/{formId}/questions/reorder Bulk-update order_index

Responses

Method Path Description
POST /api/forms/{formId}/responses Public โ€” submit a filled-out form
GET /api/forms/{formId}/responses List all responses for a form
GET /api/responses/{id} Get one response with all its answers

Stats

Method Path Description
GET /api/forms/{formId}/stats Per-question choice counts / averages

๐Ÿ“ธ Screenshots

Dashboard Form Builder
Dashboard Form Builder
Responses Dashboard Respondent Page
Responses Dashboard Respondent Page

โš™๏ธ Installation & Setup

Backend

cd backend
python -m venv venv && source venv/bin/activate   # optional but recommended
pip install -r requirements.txt
cp .env.example .env                                # adjust if needed
python seed.py                                       # seeds 2 forms + 21 responses
uvicorn app.main:app --reload                         # runs on http://localhost:8000

Frontend

cd frontend
npm install
cp .env.example .env.local                # NEXT_PUBLIC_API_URL=http://localhost:8000
npm run dev                                 # runs on http://localhost:3000

Then open http://localhost:3000 โ€” it redirects to /dashboard, which already has two seeded forms ("Job Application" and "Customer Feedback") ready to explore.


Environment Variables

backend/.env

Variable Description Default
ALLOWED_ORIGINS Comma-separated frontend origins allowed by CORS *

frontend/.env.local

Variable Description Default
NEXT_PUBLIC_API_URL URL of the FastAPI backend http://localhost:8000

Production: Set NEXT_PUBLIC_API_URL=https://formly-typeform-clone.onrender.com when deploying to Vercel.


๐ŸŽฏ Deployment Guide

Backend โ†’ Render

  1. Push this repo to GitHub.
  2. On render.com, create a New Web Service pointing at the backend/ directory (or use the included render.yaml).
  3. Build command: pip install -r requirements.txt
  4. Start command: python seed.py && uvicorn app.main:app --host 0.0.0.0 --port $PORT
  5. Set the ALLOWED_ORIGINS environment variable to your Vercel URL once you have it (e.g. https://your-app.vercel.app).
  6. Note: Render's free-tier filesystem is ephemeral โ€” the SQLite database resets on every redeploy/restart. This is an accepted, documented tradeoff for a 24-hour assignment; seed.py re-seeds automatically on each boot (it's a no-op if data already exists).

Frontend โ†’ Vercel

  1. Import the repo into vercel.com, set the root directory to frontend/.
  2. Set the environment variable NEXT_PUBLIC_API_URL to your deployed Render backend URL.
  3. Deploy โ€” Vercel auto-detects the Next.js app.

Assumptions

  • Creator authentication is simplified per the assignment spec โ€” the app assumes a single default creator with no login.
  • File-upload, payments, integrations, and team collaboration are shown as "Coming soon" placeholders where relevant, as explicitly permitted by the assignment.
  • SQLite is used per the required tech stack; see the Render note above regarding persistence on free-tier hosting.

โš ๏ธ Note

The backend is hosted on Render's free tier.

If the API has been inactive for some time, the first request may take 30โ€“60 seconds while the server wakes up.


๐Ÿ”ฎ Future Improvements

Bonus features, in priority order (not yet implemented):

  1. Export responses as CSV
  2. Partial response tracking / completion rate (the is_complete column on Responses already anticipates this)
  3. Conditional logic / branching between questions
  4. File upload question type
  5. Custom themes (colors, fonts, backgrounds)

๐Ÿ“Œ Acknowledgements

This project was developed as part of the Scalar AI Labs SDE Internship Assignment and has been further refined for portfolio and learning purposes.


๐Ÿ‘ง๐Ÿป Author

Anshika Agrawal

About

A production-ready Typeform-inspired form builder with a Next.js frontend and FastAPI backend, supporting dynamic form creation, publishing, response collection, and analytics through a clean, modern interface.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages