uidudevelopers
Domains

Courses

Courses, lectures, and enrollments — a lightweight LMS surface.

  • Use it for: Public website · Server integration
  • Reads: public token — works in the browser; Bearer for getEnrollment, listEnrollments
  • Writes: Bearer (apiKey) — server-side only
  • CLI: uidu courses
  • Auth: what each token can do and where it may run — Authentication

A lightweight LMS: courses made of ordered lectures, and the people enrolled in them. Courses and lectures are public content you can render on a website; enrollments are per-person data you read from your server.

Mental model

Course                     ← listCourses / getCourse (public token)
  ├─ name, abstract, cover, kind, mode, tags, publicPath
  ├─ body                  rich text — getCourse only
  └─→ Lecture              ← listLectures / getLecture (public token)
        ├─ position        order within the course
        ├─ duration        minutes
        ├─ link            external URL, e.g. the video host
        ├─ body            rich text — getLecture only
        └─ previousLecture / nextLecture   { id, name, position } | null
Enrollment                 ← listEnrollments / getEnrollment (Bearer)
  ├─ course, contact       { id, name } / { id, name, email }
  ├─ currentLecture        where the person is
  ├─ aasmState
  └─ fieldValuesByShortname   custom fields — getEnrollment only
  • kind and mode are strings from the course's settings: kind is e_learning, ilt or vilt; mode is external or internal. tags is free-form.
  • Lists are summaries. listCourses has no body; listLectures has no body and no previous/next. Fetch one record with getCourse / getLecture for those.
  • Lectures are listed workspace-wide. listLectures has no course argument: it returns lectures across every course, and you keep the ones whose course.id matches. It also returns one page, so a course whose lectures fall past that page comes back short — walk every page with paginate when you need a full course (see the first use case).
  • orderBy, scopes, from/to are passed through to listLectures as strings; orderBy: 'position' gives course order. listCourses and listEnrollments take search params (SearchParams: filters, sorting, options) instead.
  • Enrollments are private. They tie a contact to a course, so reading them needs the account Bearer (apiKey) on your server. With a public token the list comes back empty rather than failing. aasmState is the enrollment's state as a string; read it, don't hard-code a fixed set of values.
  • Writes (createCourse, updateCourse, deleteCourse) take { input: { attributes } } (plus id to update or delete), need the Bearer, and return the payload with errors — see Writes. Lectures and enrollments have no writes in the SDK yet.

Use cases

The examples import uidu from lib/uidu.ts, a client built with publicToken and apiKey and imported only from server code, as in Authentication.

Course page with its lectures

Goal: a course's page with its description and the ordered list of its lectures.

// app/courses/[id]/page.tsx
import { getCourse, paginate, ListLecturesDocument } from '@uidu/client';
import { RichText } from '@uidu/react';
import { notFound } from 'next/navigation';
import { uidu } from '@/lib/uidu';

export default async function CoursePage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const [course, lectures] = await Promise.all([
    getCourse(uidu, { id: decodeURIComponent(id) }),
    // Every lecture in the workspace, every page — then keep this course's.
    paginate(uidu, ListLecturesDocument, {
      variables: { first: 100, orderBy: 'position' },
      extract: (data) => data.currentWorkspace?.lectures,
    }),
  ]);
  if (!course) notFound();

  const courseLectures = lectures
    .filter((l) => l.course?.id === course.id)
    .sort((a, b) => (a.position ?? 0) - (b.position ?? 0));

  return (
    <article>
      <h1>{course.name ?? 'Untitled course'}</h1>
      <p>{course.abstract}</p>
      {course.body && <RichText doc={course.body} />}
      <ol>
        {courseLectures.map((lec) => (
          <li key={lec.id}>
            <a href={`/lectures/${encodeURIComponent(lec.id)}`}>{lec.name}</a>
            {lec.duration && ` (${lec.duration} min)`}
          </li>
        ))}
      </ol>
    </article>
  );
}
  • For a catalogue page, listCourses(uidu, { first: 20 }) returns the course summaries: name, abstract, cover, kind, mode, tags, publicPath (e.g. /courses/intro-to-x) and publicUrl.
  • listLectures(uidu, { first: 100, orderBy: 'position' }) is enough when the workspace has few lectures; paginate is what makes the list complete. More in Pagination & field values.
  • The sort after filtering keeps the order right even when lectures of different courses interleave across pages.

Lecture page with previous / next

Goal: one lecture's content, with links to the lectures before and after it.

// app/lectures/[id]/page.tsx
import { getLecture } from '@uidu/client';
import { RichText } from '@uidu/react';
import { notFound } from 'next/navigation';
import { uidu } from '@/lib/uidu';

const href = (id: string) => `/lectures/${encodeURIComponent(id)}`;

export default async function LecturePage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const lecture = await getLecture(uidu, { id: decodeURIComponent(id) });
  if (!lecture) notFound();

  const { course, previousLecture: prev, nextLecture: next } = lecture;

  return (
    <article>
      {course && (
        <a href={`/courses/${encodeURIComponent(course.id)}`}>{course.name}</a>
      )}
      <h1>{lecture.name}</h1>
      {lecture.duration && <p>{lecture.duration} min</p>}
      {lecture.link && <a href={lecture.link}>Watch</a>}
      {lecture.body && <RichText doc={lecture.body} />}
      <nav>
        {prev && <a href={href(prev.id)}>← {prev.name}</a>}
        {next && <a href={href(next.id)}>{next.name} →</a>}
      </nav>
    </article>
  );
}
  • previousLecture / nextLecture are { id, name, position } | null: no need to fetch the whole course to navigate.
  • getLecture also returns kind, cover, startDate / startTime (for scheduled lectures), published and draft.

Enrollment report

Goal: from your server, list who is enrolled in which course and where they are, and read one enrollment's custom fields.

// app/api/enrollments/route.ts — server only, needs the apiKey client
import { listEnrollments, getEnrollment } from '@uidu/client';
import { uidu } from '@/lib/uidu';

export async function GET() {
  const enrollments = await listEnrollments(uidu, { first: 50 });

  const rows = enrollments.map((e) => ({
    course: e.course.name,
    person: e.contact.name,
    email: e.contact.email,
    state: e.aasmState,
    at: e.currentLecture?.name ?? null,
  }));

  // One enrollment in full: its custom fields as a { shortname: value } map.
  const first =
    enrollments[0] && (await getEnrollment(uidu, { id: enrollments[0].id }));

  return Response.json({ rows, fields: first?.fieldValuesByShortname ?? null });
}
  • Empty with no error means the client has no apiKey: enrollments are invisible to a public token.
  • fieldValuesByShortname (custom fields) and currentLecture.position are on getEnrollment only; the list has currentLecture { id, name }.
  • To read every enrollment, pass ListEnrollmentsDocument to paginate.

API at a glance

Every @uidu/client function in Courses, generated from the source — the full reference has the rest of the SDK.

FunctionKindAuthCLIDescription
createCoursewriteBearercourses createCreate a course from { input: { attributes } }. Returns the payload (with errors) or null.
deleteCoursewriteBearercourses deleteDelete a course by { input: { id } }. Returns the payload (with errors) or null.
getCoursereadpublic tokencourses getOne course with its rich-text body, or null.
getEnrollmentreadBearer—One enrollment with its course, contact and custom fields (fieldValuesByShortname), or null.
getLecturereadpublic token—One lecture with its body, its course and its nextLecture / previousLecture for navigation, or null.
listCoursesreadpublic tokencourses listThe workspace's courses as a flat array, optionally filtered with search params; page with first/after.
listEnrollmentsreadBearer—Course enrollments as a flat array, optionally filtered with search params; page with first/after.
listLecturesreadpublic token—Lectures across every course as a flat array (filter by course.id yourself), with scopes, orderBy, from/to; page with first/after.
updateCoursewriteBearercourses updateUpdate a course: { input: { id, attributes } }. Returns the payload (with errors) or null.

Types: Course, Enrollment, Lecture

CLI:

Entitylistgetcreateupdatedelete
courses✓✓✓✓✓

On this page