A production-grade, multi-tenant AI chat assistant SaaS. Users sign in, chat with a tool-calling agent, watch responses stream in real time, and track their usage on a dashboard — with free AI models enabled out of the box.
- Real-time streaming — replies stream token-by-token over Server-Sent Events (SSE).
- Tool-calling agent — a LangGraph
createReactAgentthat uses real tools (calculator, web search) with a forced tool loop. - Queue-backed runs — messages are enqueued to BullMQ/Redis and processed by a separate worker, so the API never blocks.
- Per-user usage tracking — token counts and estimated cost attributed to every account.
- Multi-tenant auth — Better Auth (email/password) with fully isolated per-user data.
- Conversation management — modify, rename, and delete conversations inline.
- Public site — landing page plus Privacy Policy and Terms of Service.
┌────────────┐ /api (REST, SSE) ┌─────────────┐ BullMQ ┌──────────────┐
│ React app │ ◄────────────────────► │ Express API │ ───────────► │ BullMQ worker│
│ (Vite) │ │ (port 4000)│ ◄─────────── │ (LangGraph) │
└────────────┘ └──────┬──────┘ +events └──────┬───────┘
│ │
▼ ▼
PostgreSQL Redis (6389)
(users, conversations, (queue + pub/sub
messages, runs, usage) brokers SSE events)
- Client: React 18, Vite, TypeScript, Redux Toolkit, TanStack Query, react-router-dom, Tailwind CSS
- API: Express 4, better-auth, pino-http, Prisma
- Agent: LangGraph
createReactAgent,@langchain/openai, Zod tool schemas - Queue: BullMQ + Redis (also used as the SSE pub/sub transport)
- Models: OpenRouter (OpenAI-compatible endpoint) with free-tier model support
apps/
client/ # React SPA (chat UI, dashboard, landing, legal pages)
server/ # Express API (REST + SSE)
worker/ # BullMQ consumer that runs the LangGraph agent
packages/
agent/ # model wiring, tools, runAgent() graph orchestration
config/ # shared env loading (dotenv)
db/ # Prisma schema + client
queue/ # BullMQ queue + worker definitions
shared/ # shared TS types (AgentEvent, ChatMessage, RunSummary)
- Node.js 20+ (developed on 24)
- pnpm (
corepack enableornpm i -g pnpm) - A PostgreSQL instance on
localhost:5432(default connection:fsa/fsa, databasefsa) - Redis on
localhost:6389(the includeddocker-compose.ymlprovides exactly this)
# 1. Install dependencies
pnpm install
# 2. Configure environment
cp .env.example .env # then fill in OPENROUTER_API_KEY (and AUTH_SECRET in production)
# 3. Start Postgres + Redis (Redis via Docker)
# Postgres: your local install must be running on 5432
docker compose up -d redis
# 4. Generate the Prisma client and run migrations
pnpm db:generate
pnpm db:migrate # applies schema migrations to your local Postgres
# 5. Run everything (API, worker, client with HMR)
pnpm dev
# 6. Open http://localhost:5173Sign up for an account, or press Use demo account on the login screen
(demo@test.com / password123).
pnpm dev:api # API only (http://localhost:4000)
pnpm dev:worker # agent worker only
pnpm dev:client # Vite dev server (http://localhost:5173)| Variable | Description |
|---|---|
PORT |
API port (default 4000) |
CLIENT_URL |
Allowed client origin for auth/SSE (default http://localhost:5173) |
DATABASE_URL |
Prisma Postgres connection string |
REDIS_URL |
Redis connection string (default redis://localhost:6389) |
AUTH_SECRET |
Better Auth session secret — generate with openssl rand -base64 32 |
OPENROUTER_API_KEY |
OpenRouter API key (required for free-tier models) |
AGENT_MODEL |
Model id, e.g. nex-agi/nex-n2.5-pro:free (see below) |
OPENAI_BASE_URL |
OpenAI-compatible base URL (OpenRouter's API by default) |
OPENAI_API_KEY |
Optional — direct-OpenAI fallback if no OpenRouter key |
The agent runs the model with temperature: 0 and a fixed seed: 42, and AGENT_MODEL
is pinned to a specific model (nex-agi/nex-n2.5-pro:free by default). This keeps runs
reproducible: the same prompt returns the same tool calls and the same answer.
If you hit rate limits on the free tier, set AGENT_MODEL=openrouter/free instead —
OpenRouter automatically routes to whichever free model is available. You trade strict
reproducibility for availability.
- Client
POST /api/conversations/:id/messages→ API saves the message, creates a run (queued), and enqueues a BullMQ job. Returns202+runId. - Worker picks up the job, marks the run
running, and streams the LangGraph agent (streamMode: ["updates", "messages"]). - Every event (tokens, tool calls, tool results, completion) is published to a Redis
pub/sub channel; the API relays them to the browser via the SSE
GET /api/eventsstream. - On completion the worker records token usage (cost
$0for free-tier models) and the client invalidates its queries so history and the dashboard stay in sync.
| Script | Description |
|---|---|
pnpm dev |
API + worker + client (parallel, HMR) |
pnpm build |
Build all workspace packages |
pnpm typecheck |
Type-check all workspace packages |
pnpm db:generate |
Regenerate the Prisma client |
pnpm db:migrate |
Apply Prisma migrations to Postgres |
pnpm db:studio |
Open Prisma Studio |
MIT — see LICENSE.