Open Source Languages — Software Development Blueprint
1. Project Architecture
Purpose
Establish, before anyone writes a feature, exactly which process talks to which — so six people can build in parallel without stepping on each other.
The shape of the system
This project is a microservices backend behind a hand-written API, plus a fully separate frontend and a static docs site. The backend consists of 9 fully independent services, each its own deployable, each with its own database schema namespace within one shared Supabase Postgres instance, sitting behind a single API gateway.
Why this split matters
- The frontend never talks to the database directly. It only ever calls the API gateway.
- No service reaches directly into another service's database or Prisma schema — cross-service needs go over HTTP.
- Each service owns its own Prisma schema, its own migrations, and its own Postgres schema namespace.
requireAuth(from the shared@osl/sharedpackage) is mounted independently in each service's ownapp.ts, not centrally in the gateway — every service enforces its own authentication.
Component interaction diagram
Exit criteria before moving to Phase 2
- Everyone agrees each backend service is an independent unit, communicating only over HTTP
- Everyone can name which service(s) they own
- The diagrams above are understood by the whole team, not just the lead
Folder structure (per backend service)
Each service is independent, but all 9 follow the same internal layout:
backend/services/<service_name>/
src/
app.ts # middleware + route mounting
index.ts # entry point
controllers/ # request/response shaping, Zod validation
services/ # business logic, ownership checks
repository/ # the ONLY layer allowed to touch Prisma
routes/ # route definitions
types/ # Zod schemas + inferred types
utils/ # errors, logger, cross-service HTTP clients
tests/ # node:test files
prisma/
schema.prisma
prisma.config.ts
Dockerfile
backend/shared is a separate npm workspace package (@osl/shared)
holding genuinely cross-service code — currently requireAuth,
verifyToken, and the shared ApiError/ApiErrorCode types. It's
imported like any other dependency, not copy-pasted per service.
12. CI/CD Pipeline
CI/CD runs on Gitea Actions, not GitHub Actions — the repository is
Gitea-hosted, and an earlier draft of this document (written before the
team's platform was finalized) incorrectly referenced GitHub Actions
throughout. Workflow files live in .gitea/workflows/, not
.github/workflows/.
Example Gitea Actions workflow (backend service)
Gitea Actions uses the same workflow syntax as GitHub Actions (it's
compatible with the same actions/checkout, actions/setup-node, etc.) —
only the file location and the platform running it differ.
name: course-service-ci
on:
pull_request:
paths: ["backend/services/course_service/**", "backend/shared/**"]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 24 }
- run: npm ci
- run: npm run lint --workspace=backend/services/course_service
- run: npm run build --workspace=backend/services/course_service
- run: npm run test --workspace=backend/services/course_service
Notes
- Run each service's CI as its own job, triggered only by changes to that service's own folder (plus
backend/shared, since a change there can affect every service) — keeps CI fast and relevant. - Deployment triggers only on merge to
main, never on every PR push. - Add a rollback step (redeploy the previous successful build) so a bad deploy doesn't take the app down during a demo prep window.