Skip to content

Repository files navigation

Polls

Minimal working public polling application.

Features

  • List seeded public polls.
  • Open a poll and choose one option.
  • Record one vote per anonymous browser identity per poll.
  • Display aggregated vote counts and percentages.
  • Reject invalid options and duplicate votes.

Stack

  • Next.js and React
  • TypeScript and plain CSS
  • Drizzle ORM with PostgreSQL
  • Zod for server-side validation
  • Vercel-compatible Next.js API routes

Run locally

Requirements: Node.js 20.9 or newer and PostgreSQL 14 or newer.

git clone https://github.com/basecaseworks/polls.git
cd polls
npm install
cp .env.example .env.local
npm run db:migrate
npm run db:seed
npm run db:check
npm run dev

Open http://localhost:3000.

Environment variables

Copy .env.example to .env.local and set:

  • DATABASE_URL: PostgreSQL connection string used by migrations, the application, and the seed script.

The default setup uses the same PostgreSQL role for migrations and runtime. Never commit .env.local or real secrets.

Database

The application has three tables:

  • polls: required question and creation timestamp.
  • poll_options: required label, deterministic position, and a foreign key to polls.
  • votes: poll, option, anonymous voter key, and creation timestamp.

votes has a unique constraint on (poll_id, voter_key), so PostgreSQL is the final protection against duplicate voting. It also has a composite foreign key from (poll_id, option_id) to (poll_options.poll_id, poll_options.id). This means a vote cannot point at an option belonging to another poll, even if application validation is bypassed.

Apply committed migrations to an empty database with:

npm run db:migrate
npm run db:seed
npm run db:check

The seed script inserts three example polls and their options. It is safe to run again without duplicating seeded definitions.

db:check uses the configured runtime connection, verifies polls, poll_options, and votes, and checks select/insert/update/delete access without printing credentials.

The application uses ordinary PostgreSQL connections, so changing from Neon to another PostgreSQL provider only requires changing DATABASE_URL.

Anonymous voting

When a poll is opened or voted on, the server creates a cryptographically random 32-byte voter key when the basecase_polls_voter HTTP-only cookie is absent or invalid. The cookie is not based on an IP address, browser fingerprint, or account identity. It uses SameSite=Lax, a root path, a one-year lifetime, and the Secure flag in production.

This is a basic browser-identity mechanism. It is not suitable for elections, adversarial voting systems, high-security identity verification, or situations where users can clear or share cookies.

API

GET  /api/polls
GET  /api/polls/:id
POST /api/polls/:id/votes

Vote requests accept { "optionId": "..." }. The server validates both identifiers, verifies the option belongs to the poll, and derives the voter identity from the HTTP-only cookie. Invalid input returns 400, missing polls return 404, duplicate votes return 409, and unexpected database failures return 500.

Tests

Run the complete local static/test/build verification with:

npm run verify

Database-backed integration tests require DATABASE_URL or TEST_DATABASE_URL to point at a migrated PostgreSQL database. They cover listing, ordering, valid votes, aggregation, zero-vote polls, invalid cross-poll options, duplicate and concurrent votes, database constraints, and malformed API identifiers.

Deployment

Deploy the repository as a Next.js project on Vercel. Set DATABASE_URL in the Vercel project environment, run the committed migrations and seed against the production PostgreSQL database, run npm run db:check, and deploy.

Neon is a suitable hosted PostgreSQL option, but no Neon-specific application code is required.

Intentionally not included

Accounts, poll creation or editing, administration, comments, sharing widgets, charts libraries, real-time updates, multi-select voting, ranked voting, analytics, tracking, and election-grade identity or security are outside this reference implementation.

About

Minimal public polling app for voting and viewing results without accounts.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages