Xây dựng LMS hiện đại với Next.js, NestJS và PostgreSQL
Hướng dẫn kiến trúc LMS hiện đại với Next.js, NestJS và PostgreSQL, bao gồm khóa học, video HLS, điểm danh, tiến độ, bảo mật và khả năng mở rộng.
ShanverseCập nhật 11 phút đọc— lượt xemĐang tải lượt xem
EdTech
Next.js
NestJS
PostgreSQL
1. Chốt phạm vi trước khi chọn kiến trúc
Các nhóm người dùng chính:
Learner: tìm khóa học, đăng ký, xem video, làm bài, theo dõi tiến độ.
Instructor: soạn nội dung, tải video, tạo buổi học, xem thống kê.
Reviewer: duyệt nội dung trước khi xuất bản.
Organization Admin: quản lý thành viên, cohort, báo cáo và chính sách.
Platform Admin: vận hành tenant, moderation, billing và hỗ trợ.
Các bounded context quan trọng: Identity, Organization, Catalog, Course Authoring, Enrollment, Learning Delivery, Assessment, Attendance, Progress, Media, Notification và Reporting.
Bình luận
💡
Warning
Khởi đầu bằng modular monolith trong NestJS. Giữ ranh giới module và event contract rõ ràng; chỉ tách microservice khi có nhu cầu scale, deployment hoặc ownership độc lập đã được đo lường.
2. Kiến trúc tổng thể
Mermaid
Loading diagram…
Next.js: giao diện, Server Components cho dữ liệu ban đầu, Client Components cho player và tương tác; không chứa business rule cốt lõi.
Workers: transcoding, email, certificate, aggregation và export.
3. Mô hình domain khóa học
Course là danh tính ổn định. CourseVersion là snapshot có thể xuất bản. Không sửa trực tiếp nội dung mà học viên đang học; tạo draft version mới rồi publish. Enrollment phải trỏ vào version cụ thể để audit và certificate không thay đổi ngoài ý muốn.
Trạng thái nội dung nên là DRAFT → IN_REVIEW → PUBLISHED → ARCHIVED. Mọi transition được kiểm tra quyền ở server và ghi audit log.
4. Thiết kế PostgreSQL
Các bảng nền tảng:
organizations, users, memberships, roles.
courses, course_versions, sections, lessons.
media_assets, media_renditions, resources.
enrollments, lesson_progress, course_progress.
attendance_sessions, attendance_records.
assessments, attempts, answers, grades.
outbox_events, audit_logs.
Mọi bảng tenant-scoped có tenant_id; mọi unique key và foreign-key path phải giữ tenant boundary. Application authorization là lớp chính; PostgreSQL Row-Level Security có thể là lớp defense-in-depth nếu team hiểu rõ connection pooling và session context.
Index theo query thật, ví dụ (tenant_id, course_id, status), (tenant_id, learner_id, updated_at DESC). Chỉ partition event/audit tables khi dữ liệu và query chứng minh cần thiết; quá nhiều partition không tự động nhanh hơn.
5. Tiến độ học tập phải có quy tắc rõ
Không coi mỗi heartbeat video là “đã học”. Tách ba lớp:
Raw events: play, pause, seek, heartbeat, assessment submitted.
Lesson projection: watched ranges, last position, completion evidence.
Course projection: tỷ lệ hoàn thành theo rule của course version.
Client gửi heartbeat theo chu kỳ và eventId; server chống trùng. Với video, lưu các khoảng đã xem rồi merge để tránh gian lận bằng cách kéo thanh thời gian.
Server không tin tổng số giây hay trạng thái completed từ client. Completion có thể yêu cầu: xem ít nhất 90% video, đọc tài liệu bắt buộc và đạt điểm quiz. Rule được version hóa cùng CourseVersion.
Cập nhật progress dùng optimistic locking hoặc transaction với unique eventId. API retry phải idempotent. Course progress là projection có thể rebuild, không phải dữ liệu duy nhất để cấp chứng chỉ.
6. Video pipeline production-ready
Không phát trực tiếp file MP4 lớn từ NestJS:
Client xin presigned multipart-upload URL.
Tải thẳng file gốc lên private object storage.
API tạo media_asset trạng thái UPLOADED và enqueue job.
Worker quét file, probe metadata, transcode nhiều bitrate/resolution.
Tạo HLS master playlist, segments, thumbnail và subtitle.
Publish rendition bất biến; CDN phân phối gần người học.
API cấp signed cookie/URL ngắn hạn sau khi kiểm tra enrollment.
HLS chạy trên HTTP/CDN và tự thích nghi với mạng. Không đặt secret lâu dài trong manifest. Private bucket chỉ cho CDN origin truy cập; giới hạn CORS; log playback authorization. DRM chỉ cần khi mô hình nội dung thực sự yêu cầu vì chi phí và độ phức tạp cao.
Media lifecycle cần trạng thái PENDING_UPLOAD → PROCESSING → READY | FAILED → ARCHIVED, progress job và retry có backoff. Job transcoding phải idempotent theo (assetId, profileVersion).
7. Điểm danh có thể kiểm toán
Điểm danh online không nên chỉ là nút “Có mặt”. Một phiên học có:
thời gian mở/đóng do server quyết định;
enrollment/cohort hợp lệ;
mã hoặc QR ngắn hạn, rotating nonce;
record present, late, excused, absent;
source: teacher, QR, live-room, import;
actor, timestamp, reason và audit trail khi chỉnh sửa.
Không tin giờ thiết bị, geolocation hoặc IP như bằng chứng duy nhất. Nếu dùng QR, ký payload và chống replay. Cho phép giáo viên sửa có lý do thay vì âm thầm ghi đè. Attendance aggregate là projection; raw check-in và adjustment được lưu bất biến.
8. Next.js: server-first, interactive khi cần
Server Components cho catalog, outline và dashboard ban đầu.
Client Components cho video player, quiz timer, notes và realtime attendance.
Dùng NestJS làm domain API thống nhất; Route Handlers chỉ làm BFF/proxy khi có lợi rõ ràng.
Cache nội dung public/published; không cache nhầm dashboard theo user hoặc tenant.
Sau mutation, invalidation theo course/version; không dùng TTL dài cho quyền truy cập.
Token nhạy cảm nên ở cookie HttpOnly, Secure, SameSite; không để access token lâu dài trong localStorage.
9. NestJS theo module và use case
Controller mỏng; application service điều phối use case; domain policy giữ rule; repository che chi tiết persistence. Validate input bằng DTO/Zod, nhưng authorization phải chạy trên resource đã tải, không chỉ dựa vào role trong token.
Tác vụ chậm đi queue: transcoding, email, certificate, report export. BullMQ/Redis phù hợp job phân tán, nhưng kết quả nghiệp vụ vẫn commit trong PostgreSQL.
10. Transactional Outbox và event-driven workflow
Khi publish course hoặc hoàn thành lesson, không vừa commit DB vừa gửi message theo hai bước độc lập. Trong cùng transaction:
Worker đọc outbox rồi gửi notification, tạo certificate và cập nhật analytics. Consumer lưu inbox/idempotency key vì delivery thường là at-least-once. Không hứa “exactly once” cho side effect bên ngoài.
11. Multi-tenant authorization
Quyền phải xét cả tenant, membership, role, resource và quan hệ:
Learner chỉ đọc course đã publish và enrollment của chính mình.
Instructor chỉ sửa course được phân công.
Reviewer duyệt nhưng không tự nâng quyền owner.
Organization admin không truy cập tenant khác.
Không nhận tenantId từ header rồi tin trực tiếp. Derive tenant context từ host/session/membership đã xác thực, sau đó filter mọi query. Cache key và object-storage path phải chứa tenant scope. Kiểm thử IDOR theo ma trận horizontal, vertical và cross-tenant escalation.
12. API và concurrency
Các endpoint tiêu biểu:
Dùng cursor pagination cho catalog lớn. Mutation nhận idempotency key. Publish course, submit attempt và attendance adjustment có transaction boundary rõ. Dùng If-Match/version để phát hiện hai giáo viên sửa cùng một draft.
13. Learning analytics và interoperability
Tách operational database khỏi truy vấn báo cáo nặng. Outbox/CDC đẩy event đã chuẩn hóa vào warehouse. Chỉ số nên gắn với mục tiêu học tập: completion, time-to-complete, assessment mastery, drop-off theo lesson; tránh biến thời gian online thành chất lượng học.
Nếu cần tích hợp hệ sinh thái giáo dục:
LTI 1.3: launch công cụ ngoài, deep linking và đồng bộ điểm bằng OAuth 2.0/OIDC.
xAPI: ghi nhận learning experiences theo statement vào Learning Record Store.
SCORM/cmi5: cân nhắc khi phải chạy gói nội dung chuẩn có sẵn.
Version event schema và quản trị dữ liệu cá nhân trước khi gửi analytics ra hệ thống khác.
14. Bảo mật và quyền riêng tư
MFA cho admin/instructor; session revocation; audit thao tác nhạy cảm.
Deny-by-default authorization và kiểm tra ownership ở mọi object.
Signed media access ngắn hạn; private origin; rate limit và chống credential sharing ở mức hợp lý.
Mã hóa transit/at rest, quản lý secret, backup và restore test.
Quét file upload, giới hạn MIME/size, không tin filename.
CSP, CSRF protection, XSS-safe rendering cho rich text.
Retention cho video, attendance, submissions và audit; hỗ trợ export/delete theo chính sách.
Không hiển thị bảng xếp hạng hoặc dữ liệu trẻ vị thành niên ngoài phạm vi cần thiết.
15. Khả năng mở rộng và quan sát
Scale API stateless phía sau load balancer. Read replica chỉ cho truy vấn chấp nhận độ trễ; không đọc ngay dữ liệu progress vừa ghi từ replica. Pool connection có giới hạn. Cache catalog published, không cache nguồn sự thật của grade.
Theo dõi:
p95/p99 API latency, error rate, DB pool saturation.
video startup time, rebuffer ratio, CDN hit ratio, transcode failures.
Dùng correlation ID xuyên HTTP, job và outbox. SLO nên dựa trên trải nghiệm: mở bài học, bắt đầu video, lưu tiến độ và submit bài.
16. Kiểm thử và lộ trình triển khai
Test pyramid: unit cho policy/calculation, integration với PostgreSQL thật, contract cho API/event, E2E cho enrollment → học → quiz → completion. Thêm authorization matrix, migration test, restore drill và load test giờ cao điểm.
Lộ trình:
Identity, organization, course authoring và enrollment.
Versioned publishing + learning player.
Direct upload + HLS/CDN.
Progress events, projection và resume.
Attendance và assessment.
Outbox, notification, certificate và reporting.
Multi-tenant hardening, standards integration và horizontal scaling.
Production checklist
Enrollment gắn với CourseVersion bất biến.
Video đi object storage → transcoder → HLS → CDN.
Signed media access kiểm tra enrollment.
Progress events idempotent; completion rule được version hóa.
Attendance lưu raw evidence và adjustment audit.
Tenant filter áp dụng cho DB, cache, queue và storage.
Queue job và event consumer idempotent.
Có outbox, backup/restore, observability và retention policy.
Đã test IDOR, tải đồng thời và transcode failure.
Analytics không đánh đồng hoạt động với kết quả học tập.
Kết luận
Stack Next.js + NestJS + PostgreSQL phù hợp để xây LMS hiện đại nếu ranh giới được thiết kế đúng: Next.js lo trải nghiệm, NestJS giữ domain và authorization, PostgreSQL làm nguồn sự thật, còn video và công việc nặng được tách sang object storage, CDN và workers. Bắt đầu modular monolith, chuẩn hóa event, và chỉ phân tán khi dữ liệu vận hành cho thấy cần thiết.
Hướng dẫn OAuth 2.0/OpenID Connect với Google và GitHub trong Next.js/NestJS: PKCE, state, nonce, callback, token storage, account linking, cookies và secret rotation.
Hướng dẫn cài đặt PostgreSQL trên Ubuntu chi tiết từ A-Z: cài đặt, tạo database và user, cấu hình remote access, UFW, backup, restore và xử lý lỗi thường gặp.
Hướng dẫn thiết kế database ledger kế toán đôi cho ví điện tử và ngân hàng: Debit/Credit, journal, postings, constraints, balance projection, holds, idempotency và reconciliation.