Skip to main content

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/shared package) is mounted independently in each service's own app.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.