Bayes Impact is a technology nonprofit organization building AI recommendation systems for the public interest. As part of this work, we curate public and community resource datasets and make them usable by AI agents.
A SaaS platform for building multi-agent systems, built as a Turbo monorepo with a NestJS API and a React web frontend using Auth0 for authentication.
- Node.js >= 18
- npm >= 10.5.0 (or the version specified in
package.json) - Docker and Docker Compose (for local database)
- PostgreSQL (via Docker, see below)
From the root of the repository:
npm installThis will install dependencies for all workspaces (apps and packages).
The project uses PostgreSQL with pgvector extension. The easiest way to run it locally is via Docker Compose.
cd infra/database
docker compose up -dThis will:
- Start a PostgreSQL 17 container with pgvector extension
- Create two databases:
caseai_connect(main database)caseai_connect_test(test database)
- Expose PostgreSQL on port
5432
Database Credentials:
- Host:
localhost - Port:
5432 - User:
admin - Password:
passpass - Main Database:
caseai_connect - Test Database:
caseai_connect_test
cd infra/database
docker compose downcd infra/database
docker compose logs -fCopy the example environment file:
cd apps/api
cp .env-example .envEdit .env with your configuration:
# Timezone
TZ='UTC'
# Google Cloud (optional, for AI features)
GOOGLE_APPLICATION_CREDENTIALS=../../dontsave/caseai-connect-XXX.json
# Langfuse (optional, for AI observability)
LANGFUSE_SK=XXX
LANGFUSE_PK=XXX
LANGFUSE_BASE_URL=XXX
# Database
DATABASE_URL=postgresql://admin:passpass@localhost:5432/caseai_connect
# Auth0
AUTH0_ISSUER_URL=https://bayes-impact.eu.auth0.com/
AUTH0_AUDIENCE=https://bayes-impact.eu.auth0.com/api/v2/
# Auth0 Invitations
AUTH0_ORGANIZATION_ID=org_XXX
AUTH0_CLIENT_ID=XXX
AUTH0_M2M_CLIENT_ID=XXX
AUTH0_M2M_CLIENT_SECRET=XXXRequired variables:
DATABASE_URL- PostgreSQL connection stringAUTH0_ISSUER_URL- Auth0 issuer URLAUTH0_AUDIENCE- Auth0 API audienceAUTH0_ORGANIZATION_ID- Auth0 organization ID (single org, see ADR-0001)AUTH0_CLIENT_ID- Auth0 web SPA application client ID (used in invitation links)AUTH0_M2M_CLIENT_ID- Auth0 M2M application client ID (for Management API)AUTH0_M2M_CLIENT_SECRET- Auth0 M2M application client secret
Optional variables:
GOOGLE_APPLICATION_CREDENTIALS- Path to Google Cloud service account key (for AI features)LANGFUSE_*- Langfuse configuration (for AI observability)
cd apps/web
cp .env-example .envEdit .env:
VITE_API_URL=http://localhost:3000
# Auth0
VITE_AUTH0_DOMAIN=bayes-impact.eu.auth0.com
VITE_AUTH0_CLIENT_ID=XXX
VITE_AUTH0_AUDIENCE=https://bayes-impact.eu.auth0.com/api/v2/The embedding worker uses Docling in-process for document extraction.
- Extraction logic:
apps/api/src/domains/documents/embeddings/document-text-extractor.service.ts - Worker startup health check:
apps/api/src/workers-main.ts - Shared Docling helpers:
apps/api/src/external/docling
Install Docling on your machine so docling is available in PATH.
macOS
python3 --version
python3 -m pip install --upgrade pip
python3 -m pip install docling
docling --versionLinux
python3 --version
python3 -m pip install --upgrade pip
python3 -m pip install docling
docling --versionIf your distro blocks global Python installs, use a virtual environment:
python3 -m venv .venv-docling
source .venv-docling/bin/activate
python3 -m pip install --upgrade pip
python3 -m pip install docling
docling --versionWindows (PowerShell)
py --version
py -m pip install --upgrade pip
py -m pip install docling
docling --versionIf docling is not recognized, restart the terminal and make sure your Python Scripts directory is in PATH.
Docling-related environment variables:
DOCUMENT_EXTRACTOR_DOCLING_ENABLED(default:true)DOCUMENT_EXTRACTOR_DOCLING_COMMAND(default:docling)DOCUMENT_EXTRACTOR_DOCLING_TIMEOUT_MS(default:60000for extraction; worker health check uses10000fallback if unset)
Before running the API, you need to apply database migrations:
cd apps/api
npm run migration:runThis will apply all pending migrations to the caseai_connect database.
Migration Commands:
npm run migration:run- Run all pending migrationsnpm run migration:revert- Revert the last migrationnpm run migration:show- Show migration statusnpm run migration:generate -- -n MigrationName- Generate a new migration from entity changesnpm run migration:create -- migrations/MigrationName- Create an empty migration file
The invitation flow requires Auth0 redirects that work best with a stable local domain and HTTPS. Both the API and web app auto-detect certificates and enable HTTPS when they are present.
You do not need to update your hosts file:
- macOS / Linux: no
/etc/hostschange required - Windows: no
%SystemRoot%\System32\drivers\etc\hostschange required
Create the certificate directory and generate a certificate valid for both localhost and connect.localhost:
Windows:: install openssl if not already installed (by ex from :https://slproweb.com/products/Win32OpenSSL.html)
mkdir -p apps/api/.certs
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout apps/api/.certs/key.pem \
-out apps/api/.certs/cert.pem \
-days 365 \
-subj "/CN=connect.localhost" \
-addext "subjectAltName=DNS:connect.localhost,DNS:localhost,IP:127.0.0.1,IP:::1"Note: The
.certs/directory is shared between the API and the web app. Vite reads certs fromapps/api/.certs/(seeapps/web/vite.config.ts). The*.pemfiles are already in.gitignore.
Browsers reject self-signed certificates by default. You need to add the certificate to your system's trust store.
macOS:
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain \
apps/api/.certs/cert.pemAfter running this command, restart your browser. The certificate will be trusted system-wide.
Windows:
certutil.exe -addstore -f "Root" "./apps/api/.certs/cert.pem"
Import-Certificate -FilePath "<SET ROOT HERE>\apps\api\.certs\cert.pem" -CertStoreLocation "Cert:\LocalMachine\Root"Then restart your computer
Linux (Ubuntu/Debian):
sudo cp apps/api/.certs/cert.pem /usr/local/share/ca-certificates/connect-localhost.crt
sudo update-ca-certificatesTip: If you see "Your connection is not private" in Chrome after trusting the cert, try visiting
https://connect.localhost:3000directly in the browser first and accepting the certificate, then reload the web app.
Once HTTPS is set up, update your .env files to use https://connect.localhost:
apps/web/.env:
VITE_API_URL=https://connect.localhost:3000Auth0 Dashboard:
- Update Application Login URI to
https://connect.localhost:5173 - Update Allowed Callback URLs to include
https://connect.localhost:5173 - Update Allowed Logout URLs to include
https://connect.localhost:5173 - Update Allowed Web Origins to include
https://connect.localhost:5173
From the root:
npm run devThis will start all apps in watch mode using Turbo.
- With HTTPS (certs present): API at
https://connect.localhost:3000, web athttps://connect.localhost:5173 - Without HTTPS (no certs): API at
http://localhost:3000, web athttp://localhost:5173
API:
cd apps/api
npm run devWeb frontend:
cd apps/web
npm run devUse this when you want to validate that both runtime images boot correctly with Postgres and Redis.
From the repository root:
# Build images and start smoke stack
make docker-smoke-up PROJECT=connect REGION=eu
# Check service status and fail if api/workers exited
make docker-smoke-check PROJECT=connect REGION=eu
# Inspect logs
make docker-smoke-logs PROJECT=connect REGION=eu
# Tear down stack and volumes
make docker-smoke-down PROJECT=connect REGION=euNotes:
- The smoke stack is defined in
infra/docker-compose.api-workers-smoke.yaml. - API uses Docker target
api-runtime; workers useworkers-runtime. - Smoke stack ports:
- API:
http://localhost:3003 - Postgres:
localhost:55432 - Redis:
localhost:56379
- API:
From the root:
npm run testcd apps/api
npm testcd apps/api
npm run test:watchFrom the root:
npm run test:e2eOr from the API directory:
cd apps/api
npm run test:e2eThe test database (caseai_connect_test) is automatically created when you start the Docker Compose service. Before running tests, make sure migrations are applied to the test database:
cd apps/api
npm run migration:test:runTest Migration Commands:
npm run migration:test:run- Run migrations on test databasenpm run migration:test:revert- Revert last migration on test databasenpm run migration:test:show- Show migration status on test database
Note: Tests use a separate database (caseai_connect_test) to avoid interfering with development data. The test database configuration is loaded from apps/api/.env.test (if it exists) or uses the same connection string with a different database name.
If you've modified TypeORM entities and want TypeORM to generate the migration automatically:
cd apps/api
npm run migration:generate -- -n MigrationNameThis will create a new migration file in apps/api/src/migrations/ based on the differences between your entities and the current database schema.
Example:
npm run migration:generate -- -n AddUserEmailIndexIf you need to write a custom migration manually:
cd apps/api
npm run migration:create -- migrations/AddCustomFeatureThis creates an empty migration file that you can fill in with your custom SQL or TypeORM migration code.
Example Migration Structure:
import type { MigrationInterface, QueryRunner } from "typeorm"
export class AddCustomFeature1234567890000 implements MigrationInterface {
name = "AddCustomFeature1234567890000"
public async up(queryRunner: QueryRunner): Promise<void> {
// Your migration code here
}
public async down(queryRunner: QueryRunner): Promise<void> {
// Your rollback code here
}
}-
Always test migrations on the test database first:
npm run migration:test:run
-
Check migration status before applying:
npm run migration:show
-
Write reversible migrations - always implement the
down()method to allow rollbacks. -
Use transactions - TypeORM runs migrations in transactions by default, so if a migration fails, it will be rolled back.
-
Test rollbacks:
npm run migration:revert
From the root:
# Check and auto-fix linting and formatting issues
npm run biome:check
# Check only (CI mode)
npm run biome:ci
# Format only
npm run formatFrom the root:
npm run typecheckcaseai-connect/
├── apps/
│ ├── api/ # NestJS API
│ ├── web/ # React web frontend
│ └── mcp-server/ # MCP server for Claude Desktop
├── packages/
│ ├── api/ # Shared API types and DTOs
│ ├── jest-config/ # Shared Jest configuration
│ ├── typescript-config/# Shared TypeScript configuration
│ └── ui/ # Shared UI components
├── infra/
│ └── database/ # Docker Compose setup for PostgreSQL
└── package.json # Root package.json with workspace scripts
-
Check if Docker is running:
docker ps
-
Verify database is accessible:
psql postgresql://admin:passpass@localhost:5432/caseai_connect
-
Check database logs:
cd infra/database docker compose logs -f
-
Migration fails to run:
- Check that the database is running
- Verify
DATABASE_URLin.envis correct - Check migration files for syntax errors
-
Migration already applied:
- Check migration status:
npm run migration:show - If needed, revert and re-run:
npm run migration:revert && npm run migration:run
- Check migration status:
-
"Your connection is not private" in Chrome:
- Make sure you trusted the certificate (see step 5.3)
- Try visiting
https://connect.localhost:3000directly and accepting the certificate - Restart your browser after trusting the certificate
- On macOS, verify the cert is trusted:
security find-certificate -c "connect.localhost" /Library/Keychains/System.keychain
-
CORS errors with
https://connect.localhost:- The API's CORS config already allows
https://connect.localhost:5173. If you still get CORS errors, the browser may be blocking the request because it doesn't trust the API's certificate. - Visit
https://connect.localhost:3000directly and accept the certificate, then reload the web app.
- The API's CORS config already allows
-
Certificate expired:
- Regenerate the certificate (step 5.2) and re-trust it (step 5.3).
-
App falls back to HTTP:
- Make sure the cert files exist at
apps/api/.certs/key.pemandapps/api/.certs/cert.pem. - Both the API (
main.ts) and web (vite.config.ts) auto-detect certs — if the files are missing, they silently fall back to HTTP.
- Make sure the cert files exist at
If port 3000 is already in use:
-
Find the process using the port:
lsof -i :3000
-
Kill the process or change the port in
apps/api/src/main.ts