A minimal, self-contained voting system. Create polls, share them, and watch the results update in real time. Ships with an in-memory demo mode so it runs with zero configuration, and an optional Prisma database for persistence.
- Backend: Node.js + Express 5
- Database (optional): Prisma (SQLite locally, PostgreSQL for production)
- Frontend: Vue 3 (CDN) + Tailwind CSS (CDN), served from
public/ - Deploy: Vercel (serverless)
- Create polls with two or more options (admin only)
- One vote per poll per voter (tracked by an anonymous local voter id)
- Live percentage results with a highlighted winner
- Admin login via
x-admin-keyto create/delete polls - Automatic demo mode with sample data when no database is configured
api/index.js Vercel serverless entrypoint
lib/app.js Express app (routes + store resolution)
server.js Local development server
store.js Prisma and in-memory store implementations
prisma/ Schema and migrations
public/index.html Vue + Tailwind single-page UI
vercel.json Routes /api/* to the serverless function
npm install
cp .env.example .env # optional
npm start # http://localhost:3000Run without a database and VoteFlow starts in demo mode with sample polls
that reset on restart. The demo admin key is whatever ADMIN_KEY is set to
(default admin).
- Set
DATABASE_URLin.env(see.env.example). - Apply migrations and start:
npx prisma migrate deploy
npm startSQLite example: DATABASE_URL="file:./dev.db".
- Push this repo to GitHub/GitLab and import it at
vercel.com/new, or run
npx vercelfrom the project root. - Set environment variables in Project → Settings → Environment Variables:
ADMIN_KEY— your admin password (required in production).DATABASE_URL— optional, see below.
- Deploy. Vercel builds
api/index.jsas a single serverless function and servespublic/from its CDN.
vercel.json routes every /api/* request to the function; static files in
public/ are served directly by the CDN.
Serverless functions are ephemeral, so:
-
Without
DATABASE_URLthe app runs in demo mode. Data resets on every cold start and is not shared between instances. -
SQLite does not work on Vercel (read-only, non-persistent filesystem). Use a hosted PostgreSQL database such as Neon or Supabase, then:
- Change the datasource provider in
prisma/schema.prismatopostgresql:datasource db { provider = "postgresql" url = env("DATABASE_URL") }
- Point
DATABASE_URLat your Postgres instance. - Run the migration against it (locally, with the same URL in your env):
npx prisma migrate deploy
- Change the datasource provider in
postinstall runs prisma generate automatically during the Vercel build.
| Variable | Required | Description |
|---|---|---|
ADMIN_KEY |
No* | Admin password. Defaults to admin. |
DATABASE_URL |
No | Database connection string. Omit to run in demo mode. |
PORT |
No | Local server port. Defaults to 3000. Not used on Vercel. |
* Strongly recommended in production.
All endpoints return JSON. Admin endpoints require an x-admin-key header.
| Method | Path | Auth | Description |
|---|---|---|---|
GET |
/api/mode |
— | Current mode (demo/database) |
POST |
/api/admin/login |
— | Validate the admin key |
GET |
/api/polls |
— | List polls |
GET |
/api/polls/:id |
— | Poll detail with results |
POST |
/api/polls |
Admin | Create a poll |
POST |
/api/polls/:id/vote |
— | Cast a vote |
DELETE |
/api/polls/:id |
Admin | Delete a poll |
Example:
curl -X POST localhost:3000/api/polls \
-H "x-admin-key: admin" -H "Content-Type: application/json" \
-d '{"question":"Tabs or spaces?","options":["Tabs","Spaces"]}'ISC