Developing a Course Builder for LMS
A course builder is an interface for instructors to create course structure: add sections, lessons of different types (video, text, quiz, assignment), configure progression order, and unlock conditions. The key goal is making it intuitive without technical knowledge.
Data Model
CREATE TABLE courses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title VARCHAR(500) NOT NULL,
description TEXT,
instructor_id UUID REFERENCES users(id),
status VARCHAR(50) DEFAULT 'draft',
settings JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE TABLE course_sections (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
course_id UUID REFERENCES courses(id) ON DELETE CASCADE,
title VARCHAR(500) NOT NULL,
position INTEGER NOT NULL
);
CREATE TABLE lessons (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
section_id UUID REFERENCES course_sections(id) ON DELETE CASCADE,
title VARCHAR(500) NOT NULL,
type VARCHAR(50) NOT NULL,
content JSONB NOT NULL DEFAULT '{}',
position INTEGER NOT NULL,
unlock_condition JSONB
);
Course Management API
// Create lesson
app.post('/api/courses/:courseId/sections/:sectionId/lessons', authenticate, async (req, res) => {
const { title, type, position } = req.body;
const defaultContent: Record<string, unknown> = {
video: { videoUrl: '', duration: 0, subtitles: [] },
text: { body: '' },
quiz: { questions: [], passingScore: 70, timeLimit: null },
assignment: { description: '', maxScore: 100, dueDate: null, rubric: [] },
};
const lesson = await db.lessons.create({
sectionId: req.params.sectionId,
title,
type,
content: defaultContent[type] ?? {},
position: position ?? await getNextPosition(req.params.sectionId),
});
res.json(lesson);
});
// Reorder lessons (drag-and-drop)
app.patch('/api/courses/:courseId/reorder', authenticate, async (req, res) => {
const { sections } = req.body;
await db.transaction(async (trx) => {
for (const section of sections) {
for (const lesson of section.lessons) {
await trx.query(
'UPDATE lessons SET position = $1, section_id = $2 WHERE id = $3',
[lesson.position, section.id, lesson.id]
);
}
}
});
res.json({ ok: true });
});
React Builder Component
import { DndContext, closestCenter, DragEndEvent } from '@dnd-kit/core';
import { SortableContext, verticalListSortingStrategy, arrayMove } from '@dnd-kit/sortable';
function CourseBuilder({ courseId }: { courseId: string }) {
const [sections, setSections] = useState<Section[]>([]);
const handleDragEnd = async (event: DragEndEvent) => {
const { active, over } = event;
if (!over || active.id === over.id) return;
// Reorder sections or lessons
const reordered = arrayMove(sections,
sections.findIndex(s => s.id === active.id),
sections.findIndex(s => s.id === over.id)
).map((s, i) => ({ ...s, position: i }));
setSections(reordered);
await saveOrder(reordered);
};
const LESSON_TYPES = [
{ type: 'video', label: 'Video Lesson', icon: '🎬' },
{ type: 'text', label: 'Text Lesson', icon: '📄' },
{ type: 'quiz', label: 'Quiz', icon: '📝' },
{ type: 'assignment', label: 'Assignment', icon: '📋' },
{ type: 'live', label: 'Live Lesson', icon: '📡' },
];
return (
<div className="max-w-4xl mx-auto p-6">
<DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext items={sections.map(s => s.id)} strategy={verticalListSortingStrategy}>
{sections.map(section => (
<SortableSection key={section.id} section={section} />
))}
</SortableContext>
</DndContext>
<button onClick={addSection} className="mt-4 w-full border-2 border-dashed border-gray-300 py-4">
+ Add Section
</button>
</div>
);
}
Timeframe
Basic course builder (sections + lessons + drag-drop) — 1–2 weeks. With preview, conditional unlocks, and publishing — 3–4 weeks.







