Full-stack B2B lead generation app: FastAPI + Celery + Redis (backend) and Next.js (frontend). Leads are scraped from Apollo.io via Apify and returned as real, user-selected data columns (no fabricated fields).
- Python 3.11+ (backend uses a
venv/already present inbackend/) - Node 18+ and npm (frontend)
- A running PostgreSQL (the app connects to
DATABASE_URLinbackend/.env) - A running Redis (broker + result backend for Celery)
- API keys in
backend/.env:GROQ_API_KEY(AI) andAPIFY_TOKEN(Apollo)
Port note: the frontend expects the API at http://localhost:8002 (
frontend/lib/api.tsdefault +APP_URLin config). Run uvicorn on 8002.
Choose one:
Docker (recommended):
docker compose up -d redisWindows native (no Docker): install Memurai (Redis for Windows) and start it, or run Redis inside WSL2:
wsl -d Ubuntu
sudo apt update && sudo apt install -y redis-server
sudo service redis-server startVerify: redis-cli ping should reply PONG.
cd backend
.\venv\Scripts\activate # Windows (or: source venv/bin/activate on Linux/mac)
pip install -r requirements.txt # first time only
uvicorn app.main:app --reload --port 8002API docs: http://localhost:8002/docs
The worker runs the long lead-generation jobs in the background.
On Windows use the solo pool (prefork is unreliable on Windows):
cd backend
.\venv\Scripts\activate
celery -A app.workers.celery_app.celery_app worker --loglevel=info --pool=solo(On Linux/mac you can drop --pool=solo.)
Optional monitoring dashboard:
pip install flower
celery -A app.workers.celery_app.celery_app flower --port 5555cd frontend
npm install # first time only
npm run dev| Terminal | Command |
|---|---|
| A | cd backend && uvicorn app.main:app --reload --port 8002 |
| B | cd backend && celery -A app.workers.celery_app.celery_app worker --loglevel=info --pool=solo |
| C | docker compose up -d redis (or start Redis natively) |
| D | cd frontend && npm run dev |
When creating a project you pick exactly which real Apollo.io fields you want (Company, Contact, Social). These are pulled straight from Apollo — never invented by an LLM. If a field is empty, Apollo simply did not have it.
- "Refresh from Apollo" button in the column picker runs a 1-lead live probe and adds any raw Apollo field we did not already map, so the picker always reflects what Apollo actually returns ("give me whatever the user wants").
- "Enhance with AI" is an optional toggle that appends AI-generated notes (icebreaker, etc.) on top of the real data.
Tables are created automatically on first run (SQLAlchemy create_all). To seed
sample data: python backend/scripts/seed_db.py.