Appearance
🔄 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
| Concern | Choice | Reason |
|---|---|---|
| Runtime | Node.js 20+ | LTS, native ESM, great ecosystem |
| Language | TypeScript (strict) | End-to-end type safety, better DX |
| API layer | GraphQL (Apollo Server 4) | Flexible queries for a feature-rich UI; avoids over/under-fetching |
| Relational DB | PostgreSQL via Prisma | Structured relational data (users, jobs, profiles) |
| Document DB | MongoDB via Mongoose | Flexible documents (activity logs, media metadata, AI results) |
| Auth | JWT + optional Firebase/Auth0 | Stateless auth; social login via provider |
| Storage | AWS S3 | Media uploads (reels, headshots) |
| AI / Search | OpenAI + OpenSearch/Weaviate | Semantic talent matching, job flier generation |
| Caching | Redis | Query 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 prodRequest 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 ResponseAuthentication 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):
- Salary alignment
- Work authorization
- Physical attributes
- Notable works relevance
- Production experience
- Awards & nominations
- Dream gig alignment
- Language / accent proficiency
- 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
- Add types/queries/mutations to
src/graphql/schema/index.ts - Create
src/graphql/resolvers/<domain>.ts - Register the resolver in
src/graphql/resolvers/index.ts - Add Prisma models to
prisma/schema.prismaand runpnpm db:migrate - Create service functions in
src/services/<domain>/ - Write tests in
src/services/<domain>/__tests__/