Getting Started
From a fresh clone to a running stack — what runs where, the Taskfile, local infra in Docker, the apps on your host, and the type bus.
One codebase, three apps — server (apps/server), worker (apps/worker), audit (apps/audit). The thing to get straight before anything else is what runs where, because only half the stack is on your machine.
What runs where
| Piece | Where, in development |
|---|---|
| Postgres | Hosted on Neon. There is no local Postgres container — you point DATABASE_URL at a Neon database. |
| RabbitMQ | Hosted on CloudAMQP. Same — no local container. |
| Redis | Local Docker container. BullMQ queues and the Centrifugo engine. |
| Centrifugo | Local Docker container, the realtime broker. See How realtime works. |
| server / worker / audit | Your terminal, in watch mode. |
Docker does not give you a database
docker-compose.yaml defines Redis, Centrifugo, and the three apps — no Postgres, no RabbitMQ. If a guide (including this repo's README) tells you Docker brings up a database, it is out of date. Get connection strings from the team before you start.
Step 1: Toolchain
| Tool | Target | Why |
|---|---|---|
| Node.js | LTS 22 | Dockerfile builds on node:22-alpine; dev scripts raise the heap to 8 GB. |
| pnpm | latest | One workspace, one install. |
| Docker + Compose | latest | Redis and Centrifugo. |
| Task | latest | The project's front door. Optional but assumed below. |
corepack enable
corepack prepare pnpm@latest --activate
brew install go-task # or see taskfile.dev/installationEvery task is a thin alias over a pnpm script, so pnpm <script> always works too. Run task with no arguments to list everything.
Step 2: Install
task setupThat runs pnpm install and scaffolds an empty .env.local (it will never clobber an existing one). One install at the root covers all three apps and installs Husky git hooks.
Chowbea installs with it. chowbea-axios is a normal dependency in package.json, so there is no separate or global install on the API side — after task setup the chowbea-axios binary and its busHandler() are both available. (The Education Hub is the repo that historically used a global CLI.)
Step 3: Fill in .env.local
There is no .env.example. task setup leaves you a file with three comment lines; everything else you fill in.
Config is validated by a Zod schema (libs/shared/src/config/env.config.ts) at boot, and a missing key stops the process with Config validation error: … naming it.
37 keys are required, not 8
The schema marks most integrations as required — OPENAI_API_KEY, AI_GATEWAY_*, CLOUDFLARE_*, R2_*, MICROSOFT_*, UNIPILE_API_KEY, RESEND_*, SENTRY_*, DOCS_*, PESAPAL_BASE_URL, PAYMENT_CREDENTIALS_ENC_KEY included. They have neither .optional() nor .default(), so the app will not boot without them. Ask the team for a filled .env.local rather than assembling one key by key.
The four you can set yourself, because they point at your own machine:
PORT=8000
HOST=0.0.0.0
NODE_ENV=development
FRONTEND_URL=http://localhost:3000 # comma-separated for multiple CORS origins
REDIS_URL=redis://localhost:6379 # matches task infra
CENTRIFUGO_URL=http://localhost:8006 # matches task infraREDIS_URL and CENTRIFUGO_URL already default to exactly these values, so you can omit them. DATABASE_URL (Neon) and RABBIT_MQ_URL (CloudAMQP) are shared credentials — never invent them, and never commit the file. Full inventory: Config & Environment.
Step 4: Start local infra
task infra # Redis + Centrifugo, detached
task infra:status # are they up?
task infra:logs # tail both
task infra:down # stop and removeThis is the fast loop: containers for the two things you don't want to install, everything else on your host where the debugger and file watcher work properly.
Centrifugo reads centrifugo.config.json from the repo root, which hardcodes the dev HMAC secret and API key that CENTRIFUGO_HMAC_SECRET / CENTRIFUGO_API_KEY default to — so they line up with no configuration. Its admin UI is at http://localhost:8006 (password admin).
Step 5: Run migrations
task migration:runSchema is TypeORM migrations under database/migrations/, applied by hand — never auto-generated and never applied on boot.
You are migrating a shared database
DATABASE_URL points at hosted Neon, not a throwaway local container. Check which database you are pointed at before running or reverting anything. Workflow: The Database.
Step 6: Run the apps
task dev # server + worker + audit together, watch modeColour-coded output: api blue, worker green, audit yellow. One at a time while iterating:
task dev:server # apps/server — the HTTP API
task dev:worker # apps/worker — jobs, and the Rover agent graph
task dev:audit # apps/audit — audit-log consumer
task email # React Email preview serverStep 7: The type bus
The Education Hub generates its types from this repo. Two pipes — OpenAPI for REST, the type bus for everything Swagger cannot carry. Full picture: How Chowbea works.
task bus # extract → chowbea.bus.json
task bus:watch # re-extract on save — run this while working on shared types
task bus:check # validate without writing (the CI gate)
task bus:diff # compare against staging, fail on removed typesThe server serves the manifest at /.well-known/chowbea.json, re-reading the file when it changes — so task bus:watch plus a running server gives the Hub live types with no restart. prebuild runs extract automatically, so a build never ships a stale manifest.
Step 8: Check it works
Everything mounts under the global prefix api/v1.
| URL | What |
|---|---|
http://localhost:8000/api/v1 | The HTTP API |
http://localhost:8000/docs | Scalar — interactive reference |
http://localhost:8000/docs/swagger | Swagger UI |
http://localhost:8000/docs/swagger/json | Raw OpenAPI JSON |
http://localhost:8000/.well-known/chowbea.json | Type bus manifest |
http://localhost:8000/queues | Bull Board — BullMQ dashboard |
http://localhost:8006 | Centrifugo admin |
Open Scalar and fire a request. A response means the apps, Neon, Redis, and RabbitMQ are all wired up.
/docs* is open in development; elsewhere it sits behind HTTP Basic Auth (DOCS_USERNAME / DOCS_PASSWORD).
Logging in
- Seeding — in
development,SchoolSeedServiceensures a fixed demo school exists (the tenant users attach to). Skipped outside development. - Login — every address in
SUPERADMIN_EMAILSis provisioned as a passwordless platform admin. Sign in by requesting an emailed one-time code. - Demo data —
pnpm seed:stressstamps extra data onto the demo school. Not needed on day one.
SUPERADMIN_EMAILS grants platform admin, above any school. For a school-scoped super admin, add your email to that school's SchoolConfig.superAdminEmails. See Auth & Permissions.
Everything in Docker
The alternative mode builds and runs the three apps as containers too, with ./apps and ./libs bind-mounted so watch mode still works:
task docker:dev # foreground, builds on first run
task docker:dev:downSlower to start and awkward to attach a debugger to. Use it to reproduce a container-only problem, not for daily work.
Taskfile reference
| Task | Does |
|---|---|
task | List every task |
task setup | Install deps, scaffold .env.local |
task dev · task dev:server · task dev:worker · task dev:audit | Run apps in watch mode |
task infra · :status · :logs · :down | Redis + Centrifugo |
task docker:dev · :down | Full stack in containers |
task migration:generate NAME=add_widget · task migration:run · task migration:revert | TypeORM migrations |
task bus · :watch · :check · :diff | Type bus |
task test · :watch · :cov · :e2e | Vitest |
task lint · task format · task check | ESLint, Prettier, and lint+test as a pre-push gate |
task build | Build all three apps |
task docs · task docs:build | Compodoc on :8009 |
task sandbox:* | Rover sandbox image on GHCR |
task redeploy-railway ENVIRONMENT=staging · task sync-railway-env | Railway infra |
Where to go next
Architecture
The three apps, the shared library, and the path a request takes.
Module Anatomy
How a single feature module is wired together.
Config & Environment
Every variable, grouped, and where each is read.
The Database
TypeORM entities and the migration workflow.
How Chowbea works
The two pipes that keep the Education Hub in sync.
Add a Resource Module
Build your first feature end to end.