Course platform architecture
Course platform architecture: the decisions that matter
The architecture of a course platform comes down to 4 choices: enrollment record as the single access gate, computed from dates, upserted progress rows and cached completion, not event-per-tick tracking, embedded or progressive video in phase 1, HLS pipeline in phase 2 and server-side grading with answer keys never sent to clients. Each is explained below with the alternatives that were rejected and what you accept by choosing it.
Last checked
Enrollment record as the single access gate, computed from dates
Situation: Access depends on payment, cohort start, drip offsets, refunds and expiry; scattering these checks across routes leads to leaks and inconsistent behaviour.
Choice: One enrollments row per learner and course, and one canAccessLesson function that derives access from enrollment status, cohort/enrollment dates and lesson offsets at request time. No job flips an access flag.
- Rejected: Per-lesson unlock rows written by a scheduler: scales badly and drifts when a job fails.
- Rejected: Course-level paywall only: cannot express cohorts, previews or drip.
- Consequence: Access is correct by construction and refunds revoke immediately.
- Consequence: The access check runs on every lesson request, so it is kept to three indexed reads and cached per request.
Upserted progress rows and cached completion, not event-per-tick tracking
Situation: Video lessons produce frequent position updates; an event row per update per learner would dominate database writes.
Choice: Persist one progress row per (user, lesson) updated at most every 15 s, keep completion percent as a cached column on enrollments, and defer analytics events to a phase-2 partitioned xAPI-style table.
- Rejected: Event log of every tick: complete history but 100x the writes and a hot table.
- Rejected: Client-only progress in local storage: lost across devices and unverifiable for certificates.
- Consequence: Resume accuracy is within about 15 s.
- Consequence: Completion rules (all lessons, passing quizzes) are evaluated once at completion time, not on each read.
Embedded or progressive video in phase 1, HLS pipeline in phase 2
Situation: Video is the dominant cost and complexity in a course business, but most new platforms start with few lessons per course.
Choice: Launch with an embeddable provider or a single MP4 behind the edge cache with signed URLs and hard size caps; adopt the video-streaming playbook for transcoding and HLS when volume justifies it.
- Rejected: Build the full transcoding pipeline up front: high cost and operational load before demand is proven.
- Rejected: Third-party video hosting only, forever: fast but per-minute pricing and lock-in at scale.
- Consequence: Adaptive quality is limited until phase 2.
- Consequence: Lesson rows reference a video by URL or key, so migration is a data change, not a rewrite.
Server-side grading with answer keys never sent to clients
Situation: Quizzes gate certificates and sometimes paid credentials; client-side grading is trivially cheated.
Choice: Attempts are started and submitted through the API; the server enforces time limits and attempt caps, strips answer keys, grades in a transaction and stores the immutable result; manual grading uses a queue with rubrics.
- Rejected: Client-side quiz scoring: instant but bypassable.
- Rejected: Delegating all grading to a third-party assessment tool: loses integration with completion and certificates.
- Consequence: Quiz endpoints need deadline-burst capacity, handled by single-row writes and a gradebook projection.
- Consequence: Academic honesty beyond timing limits requires proctoring (deferred and not promised).
How the data is indexed
Indexes are chosen from the questions the app asks most often. Each line below names a table and the question its main index answers.
- courses: unique (slug) — canonical course URL.
- modules: (course_id, position) — render the syllabus in order.
- lessons: (module_id, position) — lessons of a module in order.
- cohorts: (course_id, starts_at) — upcoming cohorts of a course.
- course_orders: (buyer_id, created_at DESC) — my purchases.
- enrollments: unique (user_id, course_id) — one enrollment per learner per course, idempotent grants.
- lesson_progress: unique (user_id, lesson_id) — one row per learner per lesson, upsert only.
- assessments: (course_id, kind) — gradebook columns for a course.
What Forge64 provides underneath
Forge64 matches your description to one of 12 reference architectures, writes the plan and data model into the project, generates the app, and runs a quality gate over the code before you see it. Each app gets its own Postgres database and sign-in with no per-user fee. Hosting on Forge Cloud and Forge Payments are rolling out.
This is a reference design for the category, not a promise that every item is generated today. Built-ins such as email, background jobs, file uploads and search are coming soon, so anything here that needs them is something you add yourself for now.
FAQ
- What is the most important architecture decision for a course platform?
- Enrollment record as the single access gate, computed from dates. One enrollments row per learner and course, and one canAccessLesson function that derives access from enrollment status, cohort/enrollment dates and lesson offsets at request time. No job flips an access flag.
- Do I have to make these decisions myself?
- No. Forge AI plans them from your description and writes the decisions into the project as architecture records you can read and change.
- What do the alternatives cost me?
- For example, for "Upserted progress rows and cached completion, not event-per-tick tracking": Event log of every tick: complete history but 100x the writes and a hot table. Client-only progress in local storage: lost across devices and unverifiable for certificates.
Build your app with Forge64
One prompt to a running app with auth, database, hosting and payments. Start free, no credit card required.
Start building for free