A simple poll application that displays results in tierlist format (S, A, B, C tiers).
- Easy Poll Creation: Create polls with a title, optional description, and custom options
- Browse All Polls: View all active polls in one place with vote counts and statistics
- Simple Voting: One vote per user (tracked by IP + User-Agent hash)
- Tierlist Dashboard: Results displayed as a ranked tierlist
- S Tier (Red): Top 20% by votes
- A Tier (Orange): Next 30% by votes
- B Tier (Yellow): Next 30% by votes
- C Tier (Gray): Remaining options
- No Authentication Required: Instant voting without signup
- Share Poll Links: Easy sharing with direct poll URLs
- Frontend: SvelteKit 2 + Svelte 5
- UI: shadcn-svelte + Tailwind CSS v4
- Database: PostgreSQL 16 + Drizzle ORM
- Icons: Lucide Svelte
- Deployment: Docker Compose
pnpm installUsing Docker Compose:
docker compose up -d postgresThis starts PostgreSQL on localhost:5432 with:
- Database:
polls - Username:
postgres - Password:
postgres
Generate migration files from schema:
pnpm db:generateApply migrations to create tables:
pnpm db:migrateThis creates tables: polls, options, votes, and migration tracking.
Start the development server:
pnpm devVisit http://localhost:5173
The app will be available with:
- Home page at
/ - Browse all polls at
/polls - Create poll at
/create
Build and start all services:
docker compose up -dThis will:
- Start PostgreSQL database with persistent volume
- Build and start the application
- Generate and run database migrations automatically on startup
- Expose the app on
localhost:3000
Stop services:
docker compose downStop and remove volumes:
docker compose down -vTo expose your app publicly using Cloudflare Tunnel:
- Create a tunnel in your Cloudflare Zero Trust dashboard
- Get your tunnel token
- Create a
.envfile in the project root:
CLOUDFLARE_TUNNEL_TOKEN=your_tunnel_token_here- Start all services including the tunnel:
docker compose --env-file .env up -dThe Cloudflare tunnel will:
- Create a secure outbound connection to Cloudflare
- Expose your app on your configured domain (e.g.,
https://polls.yourdomain.com) - Handle SSL/TLS automatically
- Protect your app with Cloudflare's security features
To stop all services including the tunnel:
docker compose downBuild for production:
pnpm buildPreview production build:
pnpm previewThe application uses the following environment variable:
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
postgresql://postgres:postgres@localhost:5432/polls |
PostgreSQL connection string |
NODE_ENV |
development |
Environment mode (production/development) |
PORT |
3000 |
Port for production server |
For development, the defaults work with the Docker Compose setup. For production, set DATABASE_URL to your PostgreSQL instance.
| Command | Description |
|---|---|
pnpm db:migrate |
Apply pending migrations to the database |
pnpm db:generate |
Generate new migration files after schema changes |
Apply all pending migrations:
pnpm db:migrateAfter modifying the schema in src/lib/db/schema.ts:
pnpm db:generateThis creates SQL migration files in the drizzle/ directory.
Using Docker Compose:
docker compose exec postgres psql -U postgres -d pollsOr connect from host (requires PostgreSQL client):
psql postgresql://postgres:postgres@localhost:5432/pollsCommon PostgreSQL commands:
\dt -- List all tables
\d polls -- View table structure
SELECT * FROM polls; -- Query all polls
SELECT COUNT(*) FROM votes;-- Count total votes
\q -- Exit psqlsrc/
├── lib/
│ ├── components/ui/ # shadcn-svelte components (Button, Card, Badge, etc.)
│ ├── db/
│ │ ├── schema.ts # Database schema definitions
│ │ ├── index.ts # Database connection
│ │ └── migrate.ts # Migration runner script
│ ├── tier.ts # Tier calculation algorithm
│ ├── hash.ts # Voter hash generation for duplicate prevention
│ └── utils.ts # Utility functions (cn, transitions)
├── routes/
│ ├── +layout.svelte # Root layout with header
│ ├── +page.svelte # Home/landing page
│ ├── Header.svelte # Navigation header component
│ ├── polls/
│ │ └── +page.svelte # Browse all polls
│ ├── create/
│ │ ├── +page.svelte # Poll creation form
│ │ └── +page.server.ts # Create poll server action
│ └── poll/[id]/
│ ├── +page.svelte # Vote page
│ ├── +page.server.ts # Vote handling
│ └── dashboard/
│ ├── +page.svelte # Tierlist results dashboard
│ └── +page.server.ts # Results data loading
└── app.css # Global styles and Tailwind config
- Create a Poll: Add a title, description, and at least 2 options at
/create - Browse Polls: View all active polls at
/pollswith vote counts and creation dates - Share the Link: Copy and send the poll URL to voters
- Vote: Each person votes once (tracked by browser/IP hash)
- View Dashboard: See results ranked in tierlist format (S/A/B/C tiers)
polls
id(TEXT, PRIMARY KEY) - Unique poll identifiertitle(TEXT, NOT NULL) - Poll question/titledescription(TEXT) - Optional poll descriptionresult_type(TEXT) - Display type (default: "TIERLIST")tier_labels(TEXT) - JSON array of tier labels (default: ["S","A","B","C"])created_at(TIMESTAMP) - Timestamp of creation (default: now())
options
id(TEXT, PRIMARY KEY) - Unique option identifierpoll_id(TEXT, FOREIGN KEY) - References polls(id)text(TEXT, NOT NULL) - Option textsort_order(INTEGER) - Display order
votes
id(TEXT, PRIMARY KEY) - Unique vote identifierpoll_id(TEXT, FOREIGN KEY) - References polls(id)option_id(TEXT, FOREIGN KEY) - References options(id)voter_hash(TEXT, NOT NULL) - SHA-256 hash for duplicate preventioncreated_at(TIMESTAMP) - Timestamp of vote (default: now())
If the app can't connect to PostgreSQL:
# Check if PostgreSQL is running
docker compose ps
# View PostgreSQL logs
docker compose logs postgres
# Restart PostgreSQL
docker compose restart postgresIf migrations fail:
- Reset the database:
docker compose down -v
docker compose up -d postgres- Delete migration folder:
rm -rf drizzle/ - Regenerate migrations:
pnpm db:generate - Run migrations:
pnpm db:migrate
If port 5432 or 3000 is already in use:
# Find process using the port
lsof -i :5432
lsof -i :3000
# Change ports in compose.yml
# For example: "5433:5432" for PostgreSQL- Real-time updates with WebSockets
- Custom tier labels and distribution
- Poll expiration dates
- Vote analytics and charts
- Export results to CSV/JSON
- Dark mode toggle
- Poll search and filtering