A data management platform that allows admins to upload CSV datasets and share them with viewer users. Built with a React + Vite frontend and an Express + Drizzle ORM backend backed by PostgreSQL, organized as a pnpm + Turborepo monorepo.
- Client — React 19, React Router 7, TanStack Query, Tailwind CSS v4, Vite
- Server — Express 5, Drizzle ORM, Better Auth, Zod
- Database — PostgreSQL 16
- Auth — Better Auth (credentials)
- Monorepo — pnpm workspaces + Turborepo
data-drop-headless/
├── apps/
│ ├── client/ # React frontend (Vite)
│ └── server/ # Express API
├── packages/
│ ├── api-schema/ # Shared Zod request/response schemas
│ └── shared/ # Shared utilities and types
├── docker-compose.yml
├── pnpm-workspace.yaml
└── turbo.json
docker compose up -dThis starts a PostgreSQL 16 instance on port 5432 with:
- User:
postgres - Password:
password - Database:
data-drop-headless
Each app ships with a .env.example file you can copy as a starting point:
cp apps/server/.env.example apps/server/.env
cp apps/client/.env.example apps/client/.envServer — apps/server/.env:
BETTER_AUTH_SECRET=your_random_secret
BETTER_AUTH_URL=http://localhost:3000
DATABASE_URL=postgresql://postgres:password@localhost:5432/data-drop-headless
SEED_DATABASE_URL=postgresql://postgres:password@localhost:5432/data-drop-headless
PORT=3000
NODE_ENV=development
CORS_ORIGIN=http://localhost:8080
COOKIE_DOMAIN=Client — apps/client/.env:
API_URL=http://localhost:3000From the repo root, a single install covers both apps and the shared packages:
pnpm installpnpm db:migratepnpm db:seedThis seeds the database with demo users and a sample eCommerce dataset. All demo accounts use the password DataDropPass123.
| Role | |
|---|---|
| demo-admin-user-01@email.com | Admin |
| demo-admin-user-02@email.com | Admin |
| demo-admin-user-03@email.com | Admin |
| demo-admin-user-04@email.com | Admin |
| demo-viewer-user-01@email.com | Viewer |
| demo-viewer-user-02@email.com | Viewer |
| demo-viewer-user-03@email.com | Viewer |
| demo-viewer-user-04@email.com | Viewer |
| demo-viewer-user-05@email.com | Viewer |
| demo-viewer-user-06@email.com | Viewer |
| demo-viewer-user-07@email.com | Viewer |
| demo-viewer-user-08@email.com | Viewer |
| demo-viewer-user-09@email.com | Viewer |
| demo-viewer-user-10@email.com | Viewer |
| demo-viewer-user-11@email.com | Viewer |
Warning: The seed script deletes all existing data before inserting. Never run it against a production database.
Run both apps together via Turbo:
pnpm devOr run them individually:
pnpm dev:client # client only — http://localhost:8080
pnpm dev:server # server only — http://localhost:3000The app will be available at http://localhost:8080.
This repo is set up for the two-app deployment shape:
fly.api.tomldeploys the Express API fromDockerfile.server.fly.web.tomldeploys the Vite build fromDockerfile.clientbehind nginx.- The API release command runs Drizzle migrations before each API deploy.
Use a real shared parent domain for auth cookies, for example:
- Web:
https://app.example.com - API:
https://api.example.com - Cookie domain:
example.com
The generated *.fly.dev hostnames are fine for smoke testing, but do not use fly.dev as COOKIE_DOMAIN.
Update these placeholders before deploying:
fly.api.toml:BETTER_AUTH_URL,CORS_ORIGIN,COOKIE_DOMAINfly.web.toml:[build.args] API_URL
Then deploy:
brew install flyctl
fly auth login
fly apps create data-drop-headless-api
fly apps create data-drop-headless-web
fly postgres create --name data-drop-headless-db --region iad
fly postgres attach data-drop-headless-db --app data-drop-headless-api
fly secrets set --app data-drop-headless-api \
BETTER_AUTH_SECRET="$(openssl rand -hex 32)"
fly certs add api.example.com --app data-drop-headless-api
fly certs add app.example.com --app data-drop-headless-web
fly deploy --config fly.api.toml
fly deploy --config fly.web.toml| Command | Description |
|---|---|
pnpm dev |
Run client + server together (Turbo) |
pnpm build |
Build all apps and packages |
pnpm typecheck |
Type-check the whole workspace |
pnpm lint / pnpm lint:fix |
Lint (and auto-fix) the whole workspace |
pnpm db:studio |
Open Drizzle Studio (visual DB browser) |
pnpm db:generate |
Generate a new migration from schema changes |
pnpm db:push |
Push schema changes directly (dev only) |