Skip to content

🔄 Synced from castyou-backend/README.md — edit it there, not here.

castyou-backend

Node.js + TypeScript + Apollo GraphQL API for the CastYou talent marketplace.

Tech Stack

  • Runtime: Node.js 20+, TypeScript (strict)
  • API: Apollo Server 4 + Express
  • DB: PostgreSQL (Prisma) + MongoDB (Mongoose)
  • Auth: JWT + Firebase Auth (social login) + TOTP 2FA
  • Storage: Cloudflare R2 (S3-compatible)
  • AI: OpenAI GPT-4o + text-embedding-3-small, vector search (Qdrant — self-hosted, same machine)
  • Cache / nonce store: Redis
  • Identity verification: face-comparison sidecar (Python/FastAPI + dlib)

Local Development

Prerequisites

ToolVersionInstall
Node.js20+nodejs.org or nvm install 20
npm10+bundled with Node
PostgreSQL15+brew install postgresql@15 or Docker
MongoDB7+brew install mongodb-community or Docker
Redis7+brew install redis or Docker
Dockeranydocker.com (optional — only for the face-comparison sidecar)

Step 1 — Clone and install

bash
git clone <repo-url> castyou-backend
cd castyou-backend
npm install

Step 2 — Set up environment variables

bash
cp .env.example .env

Open .env and fill in the required values:

env
# PostgreSQL — database must exist before running migrations
DATABASE_URL=postgresql://castyou:castyou@localhost:5432/castyou_db

# MongoDB
MONGODB_URI=mongodb://localhost:27017/castyou

# JWT — generate secure values with: openssl rand -base64 48
JWT_SECRET=<min 32 chars>
REFRESH_TOKEN_SECRET=<min 32 chars>

# Redis
REDIS_URL=redis://localhost:6379

# Identity liveness verification — generate with: openssl rand -base64 48
LIVENESS_SECRET=<min 32 chars>
# Leave FACE_COMPARISON_URL unset locally — confirmLiveness will error but
# everything else (challenge creation, token validation, auth) works fine
# FACE_COMPARISON_URL=http://localhost:8001

# Firebase (needed for social login — Google, Facebook, LinkedIn)
# Get from Firebase console → Project Settings → Service Accounts → Generate new private key
# Then base64-encode the JSON: base64 -i serviceAccount.json | tr -d '\n'
FIREBASE_SERVICE_ACCOUNT_KEY=<base64-encoded service account JSON>

Everything else in .env.example is optional for basic local development.

Step 3 — Create the PostgreSQL database

bash
createdb castyou_db

Or with a dedicated user:

bash
psql -U postgres -c "
  CREATE DATABASE castyou_db;
  CREATE USER castyou WITH PASSWORD 'castyou';
  GRANT ALL PRIVILEGES ON DATABASE castyou_db TO castyou;
"

Step 4 — Run database migrations

bash
npm run db:migrate
# Applies all pending Prisma migrations and regenerates the Prisma client

Step 5 — Start the dev server

bash
npm run dev
# Hot-reload via tsx watch — restarts on every file save

GraphQL playground: http://localhost:4000/graphql

Step 6 — (Optional) Create an admin user

bash
npm run create-admin
# Follow the prompts to set email + password

Step 7 — (Optional) Seed test data

Creates one account of every role type plus sample jobs, an application, and a pending report — enough data to exercise every major UI surface without clicking through the app manually.

bash
pnpm seed:test          # idempotent: safe to run multiple times
pnpm seed:test:clean    # wipes all seed accounts first, then re-seeds from scratch

Test credentials (all accounts share the same password):

RoleEmailPassword
Adminadmin@seed.castyou.devTest1234!
Talenttalent@seed.castyou.devTest1234!
Producerproducer@seed.castyou.devTest1234!
Agencyagency@seed.castyou.devTest1234!
Pet Ownerpetowner@seed.castyou.devTest1234!

What else gets seeded:

  • Talent profile — Alex Rivera, Actor, Los Angeles, SAG-AFTRA, ~80% complete
  • Producer profile — Horizon Films, with 3 open jobs and 1 closed job
  • Application — Talent applied to the first open job (status: PENDING)
  • Report — one pending report from Talent against Producer (visible in /admin/reports)

All seed accounts use the email suffix @seed.castyou.dev. The --clean flag finds and deletes only accounts with that suffix — real users are never touched.

Step 8 — (Optional) Run the face-comparison sidecar

Only needed to test the full identity verification flow end-to-end.

bash
cd ../face-comparison
docker build -t castyou-face-comparison .
# First build takes 10–20 min (dlib compiles from source)

docker run -d --name face-comparison -p 8001:8001 castyou-face-comparison

Then add to your .env:

env
FACE_COMPARISON_URL=http://localhost:8001

Scripts

CommandDescription
npm run devDev server with hot reload (tsx watch)
npm run buildCompile TypeScript to dist/
npm startRun compiled server (production)
npm run typecheckTypeScript type check (no emit)
npm testRun all tests with Vitest (watch mode)
npm test -- --runRun tests once (no watch)
npm run db:migrateApply pending Prisma migrations (dev — interactive)
npm run db:deployApply migrations for production (no prompt, safe for CI)
npm run db:generateRegenerate Prisma client after a schema change
npm run db:studioOpen Prisma Studio at localhost:5555
npm run db:seedSeed the database with fixture data
pnpm seed:testSeed one account per role + sample jobs, application, and report
pnpm seed:test:cleanWipe existing seed accounts then re-seed from scratch
npm run create-adminCreate (or update) a named admin account

Environment Variables Reference

VariableRequiredDefaultDescription
NODE_ENVnodevelopmentdevelopment / test / production
PORTno4000HTTP server port
DATABASE_URLyesPostgreSQL connection string
MONGODB_URInoMongoDB connection string
JWT_SECRETyesHS256 signing key (min 32 chars)
JWT_EXPIRES_INno7dAccess token lifetime
REFRESH_TOKEN_SECRETyesRefresh token signing key (min 32 chars)
REFRESH_TOKEN_EXPIRES_INno30dRefresh token lifetime
REDIS_URLnoredis://localhost:6379Redis connection URL
LIVENESS_SECRETyesdev defaultHMAC key for liveness challenge tokens (min 32 chars)
FACE_COMPARISON_URLnohttp://face-comparison:8001URL of the face-comparison sidecar
FIREBASE_SERVICE_ACCOUNT_KEYnoBase64-encoded Firebase service account JSON
AWS_REGIONnous-east-1Cloudflare R2 / S3 region (use auto for R2)
AWS_ACCESS_KEY_IDnoR2 / S3 access key
AWS_SECRET_ACCESS_KEYnoR2 / S3 secret key
AWS_S3_BUCKETnocastyou-mediaBucket name
OPENAI_API_KEYnoGPT-4o (flier generation); embeddings only when EMBEDDINGS_PROVIDER=openai
EMBEDDINGS_PROVIDERnofastembedfastembed (local ONNX, no key) | openai | none — see docs/INFRASTRUCTURE.md
QDRANT_URLnoQdrant vector store (semantic search, AI matching, suggestions); unset = keyword-only
CORS_ORIGINSnohttp://localhost:3000Comma-separated allowed origins

Production / Staging on Coolify (Hetzner)

Production runs on a single Hetzner CX22 VPS (2 vCPU / 4 GB RAM) managed by Coolify (self-hosted PaaS). All services run as Docker containers on the same host and communicate over an internal Docker network — nothing is exposed to the internet except through Traefik.

Infrastructure overview

ServiceCoolify typeInternal hostname
Express backendApplication (Dockerfile)backend
PostgreSQL 15Databasepostgres
MongoDB 7Databasemongodb
Redis 7Databaseredis
QdrantApplication (Docker image)qdrant
Face-comparison sidecarApplication (Dockerfile)face-comparison
Traefik (reverse proxy + SSL)auto-managed by Coolify

Step 1 — Install Coolify on the VPS

bash
# SSH into the Hetzner server
ssh root@<server-ip>

# One-command install (~5 min)
curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash

Open the Coolify dashboard at http://<server-ip>:8000 and complete the setup wizard.

Step 2 — Provision the databases

In Coolify: Resources → New → Database, create three resources:

  1. PostgreSQL 15 — note the connection string (internal hostname: postgres)
  2. MongoDB 7 — note the connection string (internal hostname: mongodb)
  3. Redis 7 — note the URL (e.g. redis://redis:6379)

Enable scheduled backups for PostgreSQL and MongoDB under each resource's Backups tab (target: Cloudflare R2).

Step 3 — Deploy the backend

  1. Resources → New → Application → connect your GitHub repo, select the castyou-backend directory
  2. Set Build pack to Dockerfile
  3. Set the public Domain to api.castyou.com — Coolify provisions a Let's Encrypt certificate automatically
  4. Under Environment Variables, add all required values (use internal Docker hostnames for DB URLs):
env
NODE_ENV=production
DATABASE_URL=postgresql://castyou:<password>@postgres:5432/castyou_db
MONGODB_URI=mongodb://castyou:<password>@mongodb:27017/castyou
REDIS_URL=redis://redis:6379
JWT_SECRET=<openssl rand -base64 48>
REFRESH_TOKEN_SECRET=<openssl rand -base64 48>
LIVENESS_SECRET=<openssl rand -base64 48>
FACE_COMPARISON_URL=http://face-comparison:8001
FIREBASE_SERVICE_ACCOUNT_KEY=<base64 JSON>
AWS_REGION=auto
AWS_ACCESS_KEY_ID=<Cloudflare R2 access key>
AWS_SECRET_ACCESS_KEY=<Cloudflare R2 secret>
AWS_S3_BUCKET=castyou-media
OPENAI_API_KEY=<key>
QDRANT_URL=http://qdrant:6333
CORS_ORIGINS=https://app.castyou.com,https://castyou.com
  1. Click Deploy.

Step 4 — Run migrations on first deploy

Migrations run automatically via npm run db:deploy during container startup. To run them manually from the Coolify terminal:

bash
npm run db:deploy
# prisma migrate deploy — applies pending migrations, no interactive prompts

Step 5 — Deploy the face-comparison sidecar

  1. Resources → New → Application → same GitHub repo, select the face-comparison directory
  2. Build pack: Dockerfile
  3. No public domain — set only the internal hostname: face-comparison
  4. Add environment variable:
env
FACE_DISTANCE_THRESHOLD=0.5
  1. Deploy. First build takes 10–20 minutes — dlib compiles from source inside the container.

RAM note: Qdrant uses ~200 MB idle, the face-comparison sidecar ~300 MB. Estimated total on CX22 is ~2.3–2.6 GB of 4 GB. Upgrade to CX32 (~€13/mo) when usage consistently exceeds 3 GB.

Step 6 — Create the first admin user

From the Coolify terminal for the backend container:

bash
npm run create-admin

Subsequent deployments

Coolify redeploys automatically on every push to the configured branch (main). The sequence is:

  1. Pulls the latest commit and builds a new Docker image
  2. prisma migrate deploy runs inside the new container before traffic switches
  3. Container swap — typically ~5–10 s of downtime

To trigger a manual redeploy: Coolify dashboard → your app → Redeploy.


Project Structure

src/
├── config/        # Env validation, DB clients (Prisma, Redis), GraphQL context
├── graphql/       # Schema SDL + resolvers (one file per domain)
├── models/        # Mongoose schemas (MongoDB)
├── services/      # Business logic (auth, talent, producer, job, media, AI)
├── middleware/    # Auth guards, rate limiter
└── utils/         # Logger, helpers
prisma/
├── schema.prisma  # PostgreSQL schema
└── migrations/    # Migration history (committed — never edit manually)
../face-comparison/ # Python/FastAPI face-comparison sidecar (sibling directory)

Documentation

  • Architecture — system design, tech decisions, request lifecycle
  • Patterns — resolver pattern, service layer, error handling, conventions
  • Infrastructure — Hetzner + Coolify full setup details