如何在 Next.js 中建立支援直播課堂的 LMS
How to Build an LMS with Live Classes in Next.js
這份指南示範如何使用 Next.js、NestJS 及 Aniq UI 的 Learnio LMS 範本建立網上課程平台,涵蓋錄播課程、直播課堂、測驗、證書及付費結帳。平台由學生網站、管理員及導師控制台,以及 NestJS API 組成,可透過 Docker 一次啟動三個應用程式。範本支援英文和阿拉伯文介面及課程內容。
Originally published on the Aniq UI blog.
By the end of this guide you will know how to build an online course platform with recorded lessons, live classes, quizzes, certificates and paid checkout, using Next.js, NestJS and the Learnio LMS template from Aniq UI. You will not have to build a student site, an admin panel and an API separately.
We follow one example school from its first plan to launch. Each step names the screen or setting involved, and the commands come straight from the template's documentation. The same path works for a solo course creator, a training company, or an agency building an LMS for a client.
About this example
Ledgerline Academy is an illustrative business we use to show the steps. It is not a client story, and the numbers in it are examples, not results.
The brief
Ledgerline Academy is a small online school that teaches bookkeeping to people changing careers. Two instructors teach. The school wants to start with a few courses and grow. Here is what it needs:
A free introductory course that anyone can take, so visitors can try the school before paying.
Paid courses with recorded lessons, personal notes and downloadable worksheets.
A live class every week for each cohort, held inside the platform instead of a separate meeting link.
Quizzes with a pass mark, and assignments that an instructor marks with feedback.
A certificate at the end of a course, with a page where an employer can check it.
Two instructors who each manage only their own courses and students.
An English and an Arabic version of the whole school.
How the template covers the brief
| Need | Where it lives | What you use |
|---|---|---|
| Free and paid courses | Student site and API | Course pages and checkout; a price of 0 skips payment entirely |
| Course content | Admin dashboard | The curriculum builder with sections, lessons and draft or published states |
| Notes and worksheets | Student site | The course player, with notes and downloadable resources per lesson |
| Weekly live class | Admin dashboard, student site, your meeting server | Live classes, the course calendar, and a Jitsi Meet server you run |
| Quizzes and assignments | Admin dashboard and student site | The quiz editor, assignment grading, graded on the server |
| Certificates | Student site | Certificates page and the public verify page |
| Instructors see their own courses | Admin dashboard and API | The Instructor role with an instructor profile linked to the account |
| Taking payment | API and student site | Stripe Checkout and a signed webhook |
| English and Arabic | All three apps | /en and /ar routes, message files, courses written in both languages |
Before you start: decisions to make
Course platforms have a few decisions that are hard to undo once students and payments exist. Make them on paper first.
Free, paid, or both?
A course with a price of 0 enrolls the student immediately and never reaches a payment processor. A paid course goes through checkout and enrolls the student only after the processor confirms payment. Ledgerline uses both: a free intro to build trust, and paid courses behind it.
Recorded, live, or both?
The catalog can be filtered by live or recorded. Recorded courses need only lessons and a media bucket. Live courses also need a meeting server that you host. Decide whether live classes are part of launch, because the server is a separate piece of infrastructure with its own cost.
Who teaches, and what may they touch?
If one person does everything, you can use one super admin account. If you have several teachers, plan an instructor profile and a linked account for each.
Which payment processors?
The template supports Stripe cards and PayPal. The checkout always lists both, so plan to configure both before taking real money. If you want only one, plan a code change to the checkout.
Which languages?
English and Arabic ship ready. Course content holds exactly two languages: an English and an Arabic value for each title, description and lesson. A third interface language is possible, but a third content language means changes in the API and the admin editors.
Where do video and files live?
Course art, lesson video and handouts go to a Cloudflare R2 bucket or another S3-compatible store. Decide who owns the bucket and what you will do about backups.
What do you tell people when they buy?
Receipts and enrollment confirmations go through Resend, which needs a verified sending domain. Plan the domain and the sender address before you test email.
Step by step
Step 1: Choose the package
Learnio is three apps: a student website, an admin and instructor dashboard, and a NestJS API behind both. You can buy them together as the Full Stack package, or one at a time. The student site alone runs on bundled sample data. The admin dashboard alone runs on an in-browser mock, and most of its screens stay empty until an API is connected.
Ledgerline needs students, instructors and a database that all agree, so it takes the Full Stack package. If you already have an API, a single frontend can point at it. The commercial license covers unlimited projects, including work you deliver to a client.
Look at the My templates page: pick the ZIP that matches the package you bought.
What to check: the extracted folder for the full stack is learnio-lms, and it contains admin-dashboard, back-end, frontend and docker-compose.yml. Single-app packages extract to a folder of their own, with a Dockerfile, package.json and .env.example.
Step 2: Run everything with Docker
Download the ZIP from My templates, unzip it, and open a terminal in the learnio-lms folder. With Docker running, start the three apps:
docker compose up --build
The first start builds three images and seeds the database with demo data, so it takes a few minutes. Afterward the student site is at localhost:3030, the admin at localhost:3031, and the API at localhost:8000. If a port is busy, add SITE_PORT, ADMIN_PORT or API_PORT to a .env file next to docker-compose.yml and run the command again.
Three demo accounts let you see each side: a student (demo@learnio.com, password Demo@123), a super admin (admin@learnio.com, password Admin@123) and an instructor (emily-davis@learnio.com, password Teach@123). Sign in as each one and click around before you change anything. Without Docker, you need Node.js 22 or 24, corepack enable, and one terminal per app, API first.
This still shows the terminal opened inside the extracted folder, the place every command here assumes you are standing.
Watch the build output: three images are built, and the two frontends hold back until the API reports healthy.
The admin runs in its own terminal on port 3031 once the API is up.
This is the administrator overview you should see after signing in as the super admin: cards and charts for the platform.
What to check: open localhost:8000/api/health, which should answer with a status of ok, then sign in on each app with the demo accounts. If a page carries a notice about sample data, that app could not reach the API.
What usually goes wrong:
Docker is not running, or a port is taken. Free port 3030, 3031 or 8000, or set
SITE_PORT,ADMIN_PORTorAPI_PORT.The first start seems stuck. It is seeding the demo data, and the frontends start only when the API is healthy.
Yarn reports version 1.22. Run
corepack enableand install again.A fresh run on a database you keep does not reset anything.
docker compose down -vdeletes the volume and the next start seeds fresh demo data.
Step 3: Set the school's name, logo and colors
The brand lives in files, and the student site and the admin keep separate copies, so change both:
Student site:
src/config/site.tsholds the name, description and social handles.src/config/brand.config.tsnames the student dashboard.Admin:
src/config/brand.config.tsholds the name, short name, company and the metadata base address.Logos: replace
public/assets/brand/learnio-wordmark.pngandlearnio-mark.pngwith your own transparent PNGs at the same names.Colors:
src/styles/theme-variables.cssin each app, with light values under:rootand dark values under.dark.
The App Settings tab in the admin does not rebrand the apps. Its site_name and support_email only change the emails the API sends. Search both message folders for the old name in English and Arabic, and set the site's public address in NEXT_PUBLIC_SITE_URL.
What to check: change the name in both apps, rebuild, and look for the old name on the sign-in pages, in the footer and in the browser tab, in English and in Arabic. Replace the favicon and the sharing images too, since link previews use them.
What usually goes wrong: only one of the two apps is rebranded, or the logo is stretched because its proportions differ from the declared size. If your artwork has another shape, update the declared width and height where the sidebar brand and the logo component set them.
Step 4: Build the first course
Open Courses in the admin and create the free intro course. Add sections, then lessons inside them, and drag to reorder. A lesson can be a video, a reading, a quiz or an assignment. Attach worksheets as lesson resources, and leave the course as a draft until it is ready. Only published public courses reach the catalog.
Titles, descriptions and lessons are stored once with an English and an Arabic value, and the editors give you a tab for each. Ledgerline writes both before publishing. A free course has a price of 0 and needs no payment processor at all.
Uploads (video, handouts, course art) go to a Cloudflare R2 or other S3-compatible bucket once you set the five R2_* variables in back-end/.env. Until then the demo's bundled sample files still play, and only new uploads fail.
Build it in this order:
Create the course with its title, description, price and category, in both languages.
Add sections, then lessons inside each section, choosing a lesson type.
Attach worksheets and handouts to the lessons that need them.
Flag a lesson as a free preview if you want visitors to see a sample. Only free-preview lessons expose their content and video on the public course page.
Publish the course when it is ready. Until then it stays a draft and the public API answers not found.
Look at how sections hold their lessons, each with a handle you drag to change the order.
What to check: open the public course page in another browser. It should show the overview, syllabus, price and the enroll action. Then sign in as the demo student and open the course player to see lessons, notes and resources.
What usually goes wrong: a course is visible in the admin but not on the site because it is still a draft or is not set to public. Another common one is a missing Arabic title, which makes the Arabic page fall back to empty fields.
Step 5: Add quizzes, assignments and certificates
In the quiz editor, add questions and answer options, then set a passing score and an attempt limit, or allow unlimited attempts for practice. The server grades every attempt, so students cannot change a score in the browser. Required quizzes count toward finishing the course.
Assignments appear in the student's calendar, where they hand in a submission. The instructor opens the submissions, saves a grade, and writes feedback for that student. After every finished lesson and every passed quiz, the API re-checks whether the course is complete. When it is, the enrollment is marked complete, a certificate is issued and the student gets a notification. Anyone can then check a certificate number at /en/verify-certificate, with no account.
Look at the submission waiting to be marked: the instructor sets a grade and writes feedback addressed to that student.
What to check: take a quiz as the demo student and fail it on purpose, then pass it. Confirm the attempt limit stops you when it should. Submit an assignment from the calendar, mark it as an instructor, and look for the notification on the student side.
What usually goes wrong: a required quiz is left unpassed, so the certificate is not ready, and the student is told so. Also check that the passing score is a number the questions can actually reach.
Step 6: Take payments in test mode
Out of the box the checkout uses a built-in simulator. It contacts no processor and charges nobody, and a card number that ends in 0 is rejected, which lets you rehearse a failed payment. Run a full purchase this way first, as the demo student.
To move to Stripe, create test keys in your Stripe account and put STRIPE_SECRET_KEY, STRIPE_PUBLISHABLE_KEY and STRIPE_WEBHOOK_SECRET in back-end/.env. Then add a webhook endpoint in Stripe that points at your API's address followed by /api/webhooks/payments/stripe. A student is enrolled when the webhook confirms the payment, not when they return to the site. Without the webhook, orders stay pending.
PayPal has its own keys: PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET, PAYPAL_WEBHOOK_ID and PAYPAL_ENV. Read the warning in the mistakes section below before you go live.
This is the checkout a student sees: choose card or PayPal, and the form follows the method you picked.
What to check: buy a paid course three ways. First on the simulator, to confirm the seat appears. Then decline a payment with a card number that ends in 0 and confirm no seat is granted. Then, once Stripe test keys are in, buy again and confirm the order moves from pending to paid after the webhook arrives. The order page in the admin shows each order with its course, method and status.
What usually goes wrong:
The order stays pending. The webhook endpoint address is wrong, or
STRIPE_WEBHOOK_SECRETdoes not match that endpoint.A signed-out visitor presses pay and is sent to sign in. Paying needs a student account.
The wrong processor handles a test. A method with no keys still runs the simulator.
Step 7: Set up live classes
Live classes run on a Jitsi Meet server that you host. The template does not include one, and the hosting cost is yours. Without it, the calendar shows live classes as unavailable. Put the server's details in back-end/.env (the example file lists the variables), and the API issues the tokens that let students and instructors into the room.
An instructor schedules a session from the dashboard, and it appears in the course calendar next to assignment deadlines. Students open it from /en/dashboard/calendar. The room opens shortly before the start time, with a prejoin screen first, and the instructor gets host controls. Ledgerline schedules one session per week per cohort.
Look at how live sessions and assignment deadlines share one calendar, so a student sees the whole week in a single place.
What to check: schedule a session as an instructor, then open the student calendar and confirm the class appears. Open the class page close to its start time and confirm the room becomes available.
What usually goes wrong: the calendar marks classes unavailable because no meeting server is configured, or the room is opened too early. The room opens shortly before the class, not when it is scheduled.
Step 8: Add instructors with the right limits
Students and staff sign in at different endpoints and receive tokens that only their own routes accept. The seed creates six staff roles: Super Admin, Admin, Manager, Editor, Viewer and Instructor. The Instructor role covers live classes, assignments, curriculum, and course editing, plus a read of students, enrollments and reviews.
The key detail is the link between an admin account and an instructor profile. A linked account is also limited to its own courses: it sees only those courses and their students, and any attempt to write elsewhere gets a 403. The seeded emily-davis@learnio.com account shows the setup. Ledgerline creates both instructors this way, and keeps the super admin for the owner.
What to check: sign in as the instructor and confirm the course list shows only that instructor's courses. Then try to change another instructor's course and confirm the change is refused.
What usually goes wrong: an instructor sees too much because the account is not linked to an instructor profile. Or the reverse: a teacher cannot create lessons because the role lacks the curriculum permission. Edit the role on the roles screen and the change reaches signed-in staff.
Step 9: Connect email and, if you want, the AI assistant
Verification links, password resets, receipts and enrollment confirmations go out through Resend, in English and Arabic. Put your RESEND_API_KEY in back-end/.env and verify a sending domain with Resend. Without a key nothing breaks: each message is printed in the API log instead, which is handy while testing.
The admin dashboard also has an AI assistant. It can look up courses, instructors and enrollments, turn a single request into a course draft, including the Arabic title and description, schedule classes and draw registration charts. It runs on a Google, OpenAI or DeepSeek key that you pay for. With no key in back-end/.env, it asks the admin for a key in the browser. Treat what it creates as a draft and review it before publishing.
The chat shows a single request turned into a draft course, which an admin then reviews and edits like any other course.
What to check: trigger a receipt by buying a paid course, then look in your inbox. Ask the assistant for a small draft course and open the result in the course editor to confirm both languages were filled.
What usually goes wrong: email goes nowhere because the key is missing or the domain is not verified. Until a key exists, messages appear in the API log. The assistant also does not work on the admin's offline sample data.
Step 10: Go live
Generate a secret with
node -e "console.log(require('crypto').randomBytes(48).toString('hex'))"and use it asJWT_SECRET.In
back-end/.envsetNODE_ENV=production,CORS_ORIGIN(list both frontend addresses, separated by commas) andFRONTEND_URL(the student site).In each frontend's
.envsetNEXT_PUBLIC_API_BASE_URLwith/apion the end, setNEXT_PUBLIC_SITE_URLto the real domain, and rebuild. These values are compiled in.Set
NEXT_PUBLIC_WEBSOCKET_BASE_URLin the admin so live notifications work.If you use R2, add your bucket's public host as
NEXT_PUBLIC_MEDIA_HOSTNAME(orMEDIA_HOSTNAMEwith Compose) and rebuild.Change the published demo passwords, or start from empty tables.
For MySQL, create an empty database, set the driver and connection in back-end/.env, and run yarn seed. On a live site, yarn db:sync creates the tables without writing any rows.
What to check after going live: make a real test purchase with a small amount or a discounted course, watch the order settle, confirm the enrollment email arrives, and verify the certificate page opens for someone who is not signed in.
What usually goes wrong:
The API refuses to start with an example
JWT_SECRETor an emptyCORS_ORIGIN, and prints what to fix.A frontend still shows the sample data notice, because its API address was missing at build time.
Checkout rejects a purchase from a page in a language the API does not list. Add the language to both sides before opening it.
Running it day to day
After launch, Ledgerline's team works from a handful of screens.
Watching the platform
The overview shows cards and charts for the platform. Admins also receive notifications when a student is given a seat, finishes a course, leaves a review, registers, or when a course is published. An instructor hears only about their own courses.
Marking and replying
Instructors open the submissions waiting to be marked, save a grade, and leave feedback. Students and instructors keep a conversation per course, with history and image attachments, and an unread indicator.
Looking after orders
Each order carries a status of pending, paid, failed or refunded, and a gateway that names the processor, the built-in simulator, or a free course. Revenue figures leave free orders out of the average order value, so a free intro does not drag it down. If you need to add a seat that was paid for elsewhere, an admin can record it by hand.
An order page like this shows the course, the payment method and the payment status in one place.
Moderating reviews
Reviews move through published, pending and hidden. If you set reviews_require_approval to true, new reviews stay pending until someone with moderation rights publishes them. Each change updates the course rating and the instructor rating.
People and recycle bins
Deleting a student, instructor, course, category, review or post moves it to a recycle bin where it can be restored, so a slip is not permanent.
An example launch plan by week
This schedule is an example for Ledgerline, not a promise about how long your launch will take.
Week 1, understand. Run the stack, sign in as each role, and settle the decisions list.
Week 2, brand and first course. Rebrand both apps and build the free intro course in both languages.
Week 3, assessment and payments. Add quizzes, assignments and certificates, then test purchases on the simulator and with Stripe test keys.
Week 4, live classes and staff. Stand up the meeting server, create the instructor accounts, and run a trial class with colleagues.
Week 5, production. Set the production environment, connect email, rebuild the frontends, and make one real purchase.
Week 6, soft launch. Open the free course first, then the paid ones.
Launch checklist
Names, logos, icons and colors replaced in both apps, plus sharing images.
Courses published in both languages, with prices set.
Quizzes have reachable passing scores, and required quizzes are marked.
Stripe and PayPal both configured, with webhook subscriptions in place.
A real test purchase completed and an enrollment email received.
Meeting server running and a trial class held.
Instructor accounts linked to instructor profiles and tested.
JWT_SECRET,CORS_ORIGINandFRONTEND_URLset for production.Frontend API addresses, site address and media host set, then rebuilt.
Published demo passwords changed or removed.
Resend domain verified.
Backups planned for the database and the media bucket.
Privacy and terms pages reviewed for your own business.
Template, from scratch, or a hosted course platform
A hosted course platform
You can have a school online quickly, with hosting, video and payments handled for you. The price is ongoing fees, limits on layout and workflow, and student data on someone else's system. It is a good fit when you want to teach and do not need custom behavior.
Building from scratch
You decide everything. You also build accounts for two audiences, a curriculum editor, a lesson player, quizzes, grading, certificates, checkout with webhooks, email, live class tokens and two languages before you teach your first lesson.
Starting from a template
You get those pieces already wired, with source code you own and can change. You host it, update it and fill the gaps that this guide names: a meeting server, your own payment accounts, a mail domain. It fits a school that wants its own brand and rules, or an agency building the platform for a client.
What it costs to run
Learnio packages start from $79 for a single app, and the Full Stack package Ledgerline uses is $179. It comes with a commercial license and lifetime support. The rest is what you switch on:
Hosting for the three apps, and a database if you move past SQLite.
Stripe and PayPal charge their own processing fees. See Stripe's pricing page and the PayPal merchant fees page.
Resend for email, plus a domain you can verify.
A Jitsi Meet server for live classes, with its own hosting cost.
An S3-compatible bucket such as Cloudflare R2 for video and files.
An AI key, only if you use the assistant.
Local work is free. SQLite needs no database server, the checkout simulator charges nobody, and free courses never need a processor.
What to customise next
Categories and the catalog. Set the catalog page size with the
courses_per_pagesetting.Review moderation. Set
reviews_require_approvalto true so new reviews wait for a moderator.Blog. The admin has a blog editor, and the student site publishes the posts. It is a quiet way to bring in search traffic.
Default language. Open in Arabic by setting
defaultLocaletoar.A third language. Add a locale to each app and the API. Course content itself holds exactly two languages.
Roles. Edit what each role may do on the roles screen and the change reaches signed-in staff.
Common mistakes
Skipping the webhook
If students pay and nothing happens, the webhook is the first thing to check. Enrollment follows the processor's confirmation, so a missing subscription leaves orders pending.
Configuring Stripe but not PayPal
The checkout always offers card and PayPal. An order paid through a method with no keys uses the simulator and completes without charging anyone. Configure both before you take real orders.
Expecting live classes without a meeting server
The template provides the scheduling, tokens and room screens. The meeting server is yours to run. No server means the calendar marks classes as unavailable.
Broken course images after connecting a bucket
A frontend shows pictures only from hosts that were listed when it was built. Add your bucket's public host, with no https://, and rebuild.
Treating App Settings as a rebrand
The tab changes the emails only. Names, logos and colors come from files in each app.
FAQ
How do I build an LMS with Next.js?
Start from a template that already covers courses, progress, assessment and payment, then adapt the brand and content. Learnio gives you a Next.js 16 student site, an admin dashboard and a NestJS API that already work together.
Does it support live classes?
Yes, on a Jitsi Meet server that you run. Students and instructors join inside the platform, from a course calendar that also shows deadlines.
Can I use it for clients?
Yes. The license covers unlimited projects, including ones you hand over to a customer. You cannot resell the source code or release it as a template.
Does it support Arabic and RTL?
Yes. All three apps ship in English and Arabic, and Arabic runs right to left. Course content is entered in both languages.
Do I need a backend?
For a real school, yes. The frontends can run alone on sample data, but accounts, payments and progress need the API, which comes in the Full Stack and Backend API packages.
Can I sell courses with PayPal as well as Stripe?
The template includes PayPal integration code and a PayPal webhook route next to Stripe's. Both need your own accounts and keys.
Can I offer free courses?
Yes. A course priced at 0 enrolls the student straight away and never reaches a payment processor.
Can instructors see only their own courses?
Yes. An account linked to an instructor profile is limited to that instructor's courses and their students, and writes outside them are refused.
What happens when a payment fails or is refunded?
The processor's webhook events settle, fail or refund the order. The seat is created only when the payment is confirmed, so a failed payment never grants one.
Can I add another language?
Yes, for the interface. Add the code to the student site's locale list, to the admin's lists and to the API's supported list, then translate the message files. Course content itself holds only English and Arabic.
Try it yourself
Open the student website demo and the admin and instructor demo. Then read the Learnio documentation or visit the Learnio product page. More options are in the education and LMS templates category and the best Next.js templates guide.
Learnio is also one of the ten templates in the Next.js Templates Bundle.
來源:DEV Community · dev.to













