Skip to content
lazy devs
5 min readLazy Devs

How to build an online course platform

A founder's honest guide to building an online course platform: the MVP, the hard parts (video and access), realistic costs, and what to skip at launch.

An online course platform looks like a video site with a login. That is the easy 20 percent. The other 80 percent is making sure the right people can watch the right videos, that payments unlock access reliably, and that your content does not end up on a torrent site by Friday. If you budget for "a website with lessons," budget for the access and video parts too. That is where the real work and the real cost live.

What it is and who it is for

A course platform sells access to structured learning. That structure (a course, made of modules, made of lessons, each lesson a video, a text page, a quiz, or a downloadable) is what separates it from a plain video host or a blog with a paywall.

It is for creators, coaches, and companies who want to own the relationship and the margin instead of renting it from a Teachable or a Kajabi. The usual reasons to build: your own branding and domain, pricing or bundling those tools will not do, plugging courses into a larger product, or a per-transaction cut that has started to hurt at your volume.

The one core job it must do well: a paying student logs in and watches the lesson they paid for, on the first try, without friction. Everything else (certificates, community, analytics) is secondary to that single loop working every time.

The MVP feature set

Build the loop, not the dream. The split we would push for.

Build first:

  • Course structure: courses, modules, lessons, in an order an instructor can edit.
  • Video lessons: upload, transcode, and play back reliably on phones and laptops.
  • Auth and accounts: students and instructors, with sensible roles.
  • Payments that grant access: one-time purchase or a subscription, where a successful payment unlocks the course and a refund or cancellation removes it.
  • Progress tracking: which lessons a student finished, and where they left off.
  • A basic instructor area: create a course, add lessons, see who enrolled.

Build later:

  • Quizzes, assignments, and graded work.
  • Certificates of completion.
  • Drip scheduling (releasing lessons on a timetable).
  • Community, comments, and discussion.
  • Coupons, bundles, affiliates, and multi-currency.
  • A native mobile app. Responsive web gets you to validation faster.

Our notes on how to ship a SaaS MVP in weeks and on MVP development for startups cover the scoping discipline that keeps this list short.

The hard parts most people underestimate

Video is its own product

This one surprises everyone. Streaming video well is a real engineering problem, and "put the file in S3 and use a <video> tag" falls apart the moment a student on a train tries to watch a 1080p lesson on patchy 4G.

What you actually need: transcoding into multiple resolutions, adaptive bitrate streaming (HLS) so the player drops quality instead of buffering, a CDN so the video is served close to the viewer, and signed, expiring URLs so the stream cannot be hot-linked. Building that yourself is weeks of work and ongoing pain. We almost always reach for a managed video service (Mux, Cloudflare Stream, or api.video) for the MVP. You upload, they hand back a playable stream and a thumbnail, and you skip building a media company by accident.

Access control is the whole game

The question your code answers a thousand times a day is "is this person allowed to watch this right now?" That depends on what they bought, whether their subscription is active, whether they were refunded, and whether the content is even released yet. Wrong in one direction, paying customers are locked out and angry. Wrong in the other, your paid course is free.

Keep access as derived data, computed from enrollments and payment state, not a flag someone sets by hand. A minimal shape:

CREATE TABLE enrollments (
  id           uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id      uuid NOT NULL REFERENCES users(id),
  course_id    uuid NOT NULL REFERENCES courses(id),
  source       text NOT NULL,           -- 'purchase' | 'subscription' | 'gift'
  status       text NOT NULL,           -- 'active' | 'refunded' | 'expired'
  granted_at   timestamptz NOT NULL DEFAULT now(),
  revoked_at   timestamptz,
  UNIQUE (user_id, course_id)
);
 
-- "Can this user watch this course?" is one cheap, honest query:
SELECT 1 FROM enrollments
WHERE user_id = $1 AND course_id = $2 AND status = 'active'
LIMIT 1;

The trick is that status changes from outside your app. When Stripe says a subscription lapsed or a charge was refunded, a webhook has to flip that row. Which leads to the next trap.

Payment webhooks and access drift

Payment state lives at Stripe, access lives in your database, and webhooks connecting them can arrive late, twice, or out of order. If you grant access in the browser right after checkout instead of from the webhook, a student who closes the tab too early pays and gets nothing. Treat the webhook as the source of truth, make it idempotent, and reconcile on a schedule so the two cannot drift. Same discipline we wrote up in webhooks done right, worth reading before you wire up billing.

Piracy and content protection

You will not stop a determined screen-recorder, and chasing that perfectly is a money pit. The realistic goal is to make casual sharing annoying: signed expiring video URLs, no raw downloads for premium lessons, and optional watermarking with the viewer's email. Match the protection to what the content is worth and stop there.

The stack we would reach for

Nothing exotic. A boring, proven stack ships faster and breaks less.

  • Next.js for the frontend and most of the API, keeping marketing pages (fast for SEO) and app pages (behind auth) in one codebase. Bread and butter Next.js development for us.
  • TypeScript end to end, so one team moves across the whole thing.
  • Postgres as the source of truth for users, courses, enrollments, and progress. It handles all of this comfortably for years. We explain why in why we reach for Postgres first.
  • A managed video service (Mux or Cloudflare Stream) so you are not running a transcoding farm.
  • Stripe for payments and subscriptions, driven by webhooks.
  • A background job queue for work that must not happen inside a web request: welcome emails, transcoding, nightly access reconciliation.

If your platform is more "a real product with billing tiers" than "a creator selling one course," our work on SaaS platforms is the closer fit, and the building blocks are the same.

Rough timeline and cost

These are rough ranges for planning, not quotes. Real numbers depend on how much of the "build later" list you insist on shipping at launch.

  • A focused MVP (course structure, managed video, auth, Stripe checkout that unlocks access, progress tracking, a basic instructor area): roughly 2 to 4 months with a small senior team, in the low-to-mid five figures.
  • A fuller platform (subscriptions and tiers, quizzes and certificates, drip scheduling, coupons, a polished instructor dashboard, analytics): more like 5 to 9 months and a larger budget.

The single biggest cost lever is scope at launch. Every "while we are at it" feature (a community forum, an affiliate program, a mobile app, live cohorts) is another few weeks. Shipping narrow is worth more than any framework choice.

What to watch out for

  • Building your own video pipeline. Almost never the right call for an MVP. Use a managed service, revisit only if your volume justifies it.
  • Granting access from the browser instead of the webhook. The classic "I paid and got nothing" bug. Source of truth is the payment provider, always.
  • Treating access as a manual flag. Derive it from payment state so refunds and lapsed subscriptions take effect on their own.
  • Over-investing in DRM. Reasonable protection, then stop. Perfect protection is a project that never ends.
  • Skipping the admin tooling. With no button to refund a student or fix a broken enrollment, support becomes someone editing production by hand at 9pm.

Takeaway

Build the smallest course platform that nails one loop: a student pays, logs in, and watches the lesson they paid for, reliably, on any device. Lean on managed services for video and payments, keep access derived from payment state, and treat webhooks as gospel. Get that loop solid, then add quizzes, certificates, and community on top of something that already works.

This is exactly the kind of product we build. If you want a second opinion on your scope before you commit a budget, that is a conversation we are happy to have.

Related service

SaaS Platforms

From first login to multi-tenant scale.

Learn more

Want this built right?

This is the work we do every day. Tell us what you are building and we will show you exactly how we would ship it.

hello@lazydevsagency.com