A Next.js reimplementation of the original FeaturePulse hackathon project, migrating from Vite to a production-ready stack.
FeaturePulse uses Neon for Postgres hosting. The backend and dashboard connect to the same Neon project but via different endpoints — getting this wrong will either exhaust connection limits or break query semantics.
Neon provides two connection strings per project:
| Variable | Endpoint | Used by | Why |
|---|---|---|---|
DATABASE_URL |
Direct — ep-name-hash.region… |
Backend (src/) on Railway |
pg.Pool holds long-lived TCP connections; direct endpoint is correct. PgBouncer transaction mode is incompatible with pg.Pool's prepared statements. |
DATABASE_URL_POOLED |
Pooled — ep-name-hash-pooler.region… |
Dashboard (dashboard/) on Vercel |
Concurrent Vercel invocations each open WebSocket connections. The -pooler PgBouncer endpoint multiplexes them so direct connection limits aren't exhausted. @neondatabase/serverless is explicitly designed to work with PgBouncer. |
Both strings require ?sslmode=require.
- Create a Neon project at console.neon.tech.
- On the project dashboard, copy both connection strings shown under "Connection Details" (toggle between Direct and Pooled).
- Paste them:
| Variable | Where to set it |
|---|---|
DATABASE_URL |
Railway service → Variables tab |
DATABASE_URL_POOLED |
Vercel project → Settings → Environment Variables (all environments) |
| Both | Local .env at repo root AND dashboard/.env.local (can both point to the direct URL for local dev — no PgBouncer needed locally) |
Run the DDL in src/db/schema.sql (or your migration tool) using the direct connection string once after creating the project.
See render.yaml for the full config. Summary:
| Setting | Value |
|---|---|
| Build command | npm install && npm run build |
| Start command | node dist/index.js |
| Health check path | /health |
| Plan | Free (auto-sleeps after 15 min idle) |
To prevent auto-sleep: set up UptimeRobot (free) to GET https://<your-app>.onrender.com/health every 14 minutes.
Env vars to set in Render dashboard (Settings → Environment):
| Variable | Description |
|---|---|
APP_ID |
GitHub App numeric ID |
PRIVATE_KEY |
GitHub App private key PEM (paste verbatim, multi-line) |
WEBHOOK_SECRET |
GitHub webhook secret |
OPENROUTER_API_KEY |
OpenRouter API key |
DATABASE_URL |
Neon direct connection string (see Database section above) |
REDIS_URL |
Upstash rediss:// connection string — not the REST URL |
PORT is set automatically by Render. Do not override it.
Redis is required for BullMQ (the PR analysis job queue). Start it once before running:
docker compose up -d redisThis uses docker-compose.yml (redis:7-alpine, port 6379). Stop with docker compose down.
The BullMQ worker now starts inside the same process as the Express server (src/index.ts). You only need one terminal for the backend:
npm run dev # repo root — starts Express + embedded BullMQ workerFor the dashboard:
cd dashboard && npm run dev # starts Next.js on port 3000Note:
npm run workerhas been removed. Runningworker.tsstandalone alongsideindex.tswould create duplicate consumers on the same queue. If you later upgrade to a paid Render tier with a separate worker service, re-add"worker": "tsx src/worker.ts"topackage.jsonand remove thestartWorker()call fromsrc/index.ts.
| Service | Command | Default port |
|---|---|---|
| Backend (Express + webhooks + worker) | npm run dev (repo root) |
3001 |
| Dashboard (Next.js) | npm run dev (inside dashboard/) |
3000 |
Copy .env.example to .env (repo root) and to dashboard/.env.local, then fill in values before starting either server.