Skip to content

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

CastYou Backend — Architecture

Overview

The CastYou backend is a Node.js + TypeScript API server that exposes a GraphQL endpoint via Apollo Server 4, built on top of Express. It serves both the web frontend (Next.js landing + Vite app) and the future React Native mobile app.


Technology Decisions

ConcernChoiceReason
RuntimeNode.js 20+LTS, native ESM, great ecosystem
LanguageTypeScript (strict)End-to-end type safety, better DX
API layerGraphQL (Apollo Server 4)Flexible queries for a feature-rich UI; avoids over/under-fetching
Relational DBPostgreSQL via PrismaStructured relational data (users, jobs, profiles)
Document DBMongoDB via MongooseFlexible documents (activity logs, media metadata, AI results)
AuthJWT + optional Firebase/Auth0Stateless auth; social login via provider
StorageAWS S3Media uploads (reels, headshots)
AI / SearchOpenAI + OpenSearch/WeaviateSemantic talent matching, job flier generation
CachingRedisQuery caching, session data, rate limiting

Directory Structure

src/
├── config/
│   ├── index.ts         # Zod-validated env vars — fails fast on missing config
│   ├── database.ts      # Prisma singleton + Mongoose connect
│   └── context.ts       # GraphQL context factory — JWT → user on every request

├── graphql/
│   ├── schema/
│   │   └── index.ts     # Unified SDL — all GraphQL types, queries, mutations
│   └── resolvers/
│       ├── index.ts     # Merges domain resolver maps
│       ├── auth.ts      # register, login, refreshToken, logout
│       ├── talent.ts    # talentProfile CRUD + semantic search
│       ├── producer.ts  # producerProfile CRUD
│       └── job.ts       # job CRUD + discoverFeed

├── models/
│   ├── postgres/        # Prisma schema lives at prisma/schema.prisma
│   └── mongo/           # Mongoose schemas for flexible/event data

├── services/
│   ├── auth/            # Token generation, social OAuth, 2FA
│   ├── talent/          # Profile scoring, availability logic
│   ├── producer/        # Hiring metrics, favorite lists
│   ├── job/             # Application pipeline, deadline handling
│   ├── media/           # S3 upload, reel processing, thumbnailing
│   └── ai/
│       ├── matching.ts  # Discover-feed ranking algorithm (9-factor scoring)
│       ├── flierGenerator.ts  # OpenAI-powered job flier generation
│       └── search.ts    # Semantic / phonetic search via vector store

├── middleware/
│   ├── auth.ts          # requireAuth / requireRole helpers (throw GraphQLError)
│   ├── errorHandler.ts  # Express error boundary — AppError → structured JSON
│   └── rateLimiter.ts   # express-rate-limit: 200 req/15 min general, 20 strict

└── utils/
    └── logger.ts        # Winston — pretty in dev, JSON in prod

Request Lifecycle

HTTP Request
  → Express middleware (helmet, cors, morgan, rateLimit)
  → Apollo Server (parse + validate GraphQL operation)
  → createContext() — verify JWT, attach user + prisma to ctx
  → Resolver
    → requireAuth / requireRole guard (if needed)
    → Service layer (business logic)
    → Prisma / Mongoose / external API
  → Apollo Server (serialize response)
  → HTTP Response

Authentication Pattern

All auth is stateless JWT. The access token (7d) is sent as Authorization: Bearer <token>. A separate refresh token (30d) is exchanged at Mutation.refreshToken for a new pair.

createContext in src/config/context.ts verifies the token on every request and attaches the decoded payload ({ sub, role }) to ctx.user. Resolvers call requireAuth(ctx) or requireRole(ctx, 'PRODUCER') — these throw GraphQLError with UNAUTHENTICATED / FORBIDDEN extensions if the check fails.

Social login (Google, Facebook, LinkedIn) delegates to Firebase Auth or Auth0; the provider returns a verified identity which the backend exchanges for its own JWT pair.


Database Strategy

PostgreSQL (Prisma) handles all structured relational data:

  • Users, TalentProfiles, ProducerProfiles
  • Jobs, Applications
  • TalentGroups, Memberships

MongoDB (Mongoose) handles flexible/high-volume data:

  • Activity feeds and notifications
  • Media metadata (reel edits, AI processing results)
  • AI match scores and explain logs
  • Audit / compliance logs

The two databases are accessed from resolvers via the service layer. Resolvers should not contain raw DB calls — they call service functions which own the DB interaction.


AI / Search Architecture

The Discover Feed ranks talents against a job using a 9-factor scoring model (see spec doc):

  1. Salary alignment
  2. Work authorization
  3. Physical attributes
  4. Notable works relevance
  5. Production experience
  6. Awards & nominations
  7. Dream gig alignment
  8. Language / accent proficiency
  9. Availability

src/services/ai/matching.ts owns this logic. In production it queries a vector database (Weaviate or Pinecone) seeded with talent embeddings. In development, it falls back to DB-filtered results.

Job Flier Generation (src/services/ai/flierGenerator.ts) uses GPT-4o to extract key job details, select a template, and return a structured flier spec. The frontend renders the spec into an image via a canvas layer.


Adding a New Domain

  1. Add types/queries/mutations to src/graphql/schema/index.ts
  2. Create src/graphql/resolvers/<domain>.ts
  3. Register the resolver in src/graphql/resolvers/index.ts
  4. Add Prisma models to prisma/schema.prisma and run pnpm db:migrate
  5. Create service functions in src/services/<domain>/
  6. Write tests in src/services/<domain>/__tests__/