The SkillCert backend is responsible for managing off-chain metadata — all structured data that does not live on the blockchain. This includes user accounts, course content, enrollment records, progress tracking, quiz attempts, and reviews.
The frontend communicates with this API to display course catalogues, track learner progress, and submit reviews. Any on-chain activity (e.g. certificate issuance) is handled separately; this backend stores and serves the supporting metadata around it.
The database is PostgreSQL, accessed through TypeORM with a snake_case naming strategy applied globally.
Table: users
A User represents anyone registered on the platform. Users have one of two roles: USER (a learner) or a professor/admin role. They can optionally link a blockchain wallet address for on-chain interactions.
Key fields:
id— UUID primary keyname— display nameemail— unique login emailpassword— hashed password (bcrypt)role— enum:USERor higher privilege roleswalletAddress— optional, unique, 42-char blockchain address
Relationships:
- A user can create many courses (as a professor)
- A user can enroll in many courses
- A user can write many reviews
Table: courses
A Course is the top-level learning unit. It is created and owned by a professor (a User). It belongs to one Category and can have learning Objectives defined for it.
Key fields:
id— UUID primary keytitle,descriptionprofessorId— FK touserscategoryId— FK tocategories(nullable)
Relationships:
- Belongs to one User (professor)
- Belongs to one Category
- Has many Modules
- Has many Enrollments
- Has many Reviews
- Has many Objectives
Table: categories
A flat lookup table used to classify courses (e.g. "Web Development", "Blockchain", "Design").
Relationships:
- Has many Courses
Table: objectives
Learning objectives state what a learner will achieve after completing a course. They are cascaded with the course — deleting a course removes its objectives.
Relationships:
- Belongs to one Course
Table: modules
A Module is a named section within a course (e.g. "Introduction", "Advanced Topics"). Courses are divided into modules to group related lessons.
Key fields:
id— UUID primary keytitle,descriptioncourse_id— FK tocourses
Relationships:
- Belongs to one Course
- Has many Lessons
Table: lessons
A Lesson is an individual content unit inside a module. It can be one of three types: text, video, or quiz.
Key fields:
id— UUID primary keytitle,contenttype— enum:text | video | quizmodule_id— FK tomodules
Relationships:
- Belongs to one Module
- Has many CourseProgress records
- Has one Quiz (when type is
quiz)
Table: quizzes
A Quiz is attached to a lesson of type quiz. It holds the quiz title, description, and a collection of questions.
Key fields:
lesson_id— FK tolessons
Relationships:
- Belongs to one Lesson
- Has many Questions
Tables: questions, answers
Each Question belongs to a Quiz and has multiple Answer options. One answer is marked correct.
Relationships:
Questionbelongs to one QuizQuestionhas many Answers
Table: enrollments
An Enrollment is the join record between a User and a Course. Each user-course pair is unique (enforced via a composite unique constraint). An enrollment can be deactivated (isActive: false) without being deleted.
Key fields:
user— FK tousers(CASCADE delete)course— FK tocourses(CASCADE delete)isActive— boolean, defaults totrueenrolledAt— timestamp
Relationships:
- Belongs to one User
- Belongs to one Course
- Has many CourseProgress records
Table: course_progress
Tracks which lessons a learner has completed within an enrollment. Each record links an enrollment to a specific lesson.
Relationships:
- Belongs to one Enrollment
- Belongs to one Lesson
Table: reviews
A Review is a rating and optional written feedback left by a user on a course. The primary key is a composite key of userId + courseId, meaning one user can only review a course once.
Key fields:
userId+courseId— composite primary keyrating— integer 1–5 (validated in code)title,content— optional text fields
Relationships:
- Belongs to one User
- Belongs to one Course
Tables: references, lesson_resources
These tables store supplementary material attached to lessons — external links (references) and uploaded files or media (lesson_resources). Both are currently partially implemented (relationships are commented out in the Lesson entity).
users
├── [1:N] → courses (professor creates courses)
├── [1:N] → enrollments (user enrolls in courses)
└── [1:N] → reviews (user reviews courses)
courses
├── [N:1] → users (owned by a professor)
├── [N:1] → categories (classified under a category)
├── [1:N] → modules (divided into modules)
├── [1:N] → enrollments (students enroll)
├── [1:N] → reviews (receives reviews)
└── [1:N] → objectives (has learning objectives)
modules
├── [N:1] → courses
└── [1:N] → lessons
lessons
├── [N:1] → modules
├── [1:N] → course_progress
└── [1:1] → quizzes (when type = 'quiz')
quizzes
├── [N:1] → lessons
└── [1:N] → questions
└── [1:N] → answers
enrollments
├── [N:1] → users
├── [N:1] → courses
└── [1:N] → course_progress
course_progress
├── [N:1] → enrollments
└── [N:1] → lessons
reviews
├── [N:1] → users (composite PK: userId + courseId)
└── [N:1] → courses
The frontend communicates with this API exclusively over HTTP. There is no real-time socket layer — all data fetching is request-driven.
The typical data flows are:
Course discovery: Frontend calls GET /courses (with optional category/pagination filters) → backend queries courses joined with categories, professor, and aggregated review ratings.
Enrollment: Frontend calls POST /enrollments with a user ID and course ID → backend creates an Enrollment record, which acts as the gateway for progress tracking.
Progress tracking: As a learner completes lessons, the frontend calls the course-progress endpoints → backend upserts CourseProgress records tied to the enrollment.
Reviews: After completing a course, the frontend submits POST /reviews → backend validates the rating (1–5), checks the composite key constraint, and stores the record.
Quiz submission: Frontend submits answers via POST /quiz/:id/submit → the quiz validation service scores the attempt, stores a QuizAttempt, and returns the result.
Wallet linking: Users can optionally call PATCH /users/:id/wallet to link their blockchain wallet address, enabling on-chain certificate verification flows.