← back to all projects
Course Review Platform (PostgreSQL)
Part 13 — Relational Databases
Why This Project
A course review platform naturally models as relational data: courses belong to
platforms, have instructors (many-to-many — one instructor teaches many courses,
one course can have multiple instructors), reviews belong to users and courses,
and tags/topics are a classic many-to-many relationship. This domain makes you
use every association type (one-to-many, many-to-many with join tables) and write
queries that would be awkward in MongoDB but are natural in SQL.
What to Build
A platform where people review online courses (like a Course Report or Class Central).
Browse courses by platform (Udemy, Coursera, etc.), topic, or instructor. Each course
has reviews with ratings. Users can mark courses as "completed" or "in progress."
An Express API backed by PostgreSQL and Sequelize, with a React frontend.
Requirements
- PostgreSQL database (local via Docker or free Neon instance)
- Tables and associations:
- Platform (name, url) — has many Courses
- Course (title, url, description, platform_id) — belongs to Platform, belongs to many Instructors, belongs to many Topics, has many Reviews
- Instructor (name, bio) — belongs to many Courses (join table: course_instructors)
- Topic (name, slug) — belongs to many Courses (join table: course_topics)
- Review (rating 1-5, title, body, user_id, course_id) — belongs to User, belongs to Course
- User (username, email, passwordHash) — has many Reviews, belongs to many Courses as "enrolled" (join table: user_courses with status: completed/in-progress)
- Migrations: create all tables via Sequelize migrations, not sync()
- Write a migration that adds a column to an existing table (e.g., add "difficulty" to courses)
- Rollback a migration to practice undoing schema changes
- Queries:
- GET /api/courses — eager load platform name, instructor names, average rating, review count
- Filter: by platform, by topic, by minimum rating
- GET /api/courses/:id — full detail with all reviews, all instructors, all topics
- GET /api/instructors/:id — instructor profile with all their courses
- Frontend: browse page, course detail, submit review form, user profile with enrolled courses
Skills Practiced
PostgreSQL
Sequelize
migrations
one-to-many
many-to-many
join tables
eager loading
foreign keys
aggregation (AVG, COUNT)
relational thinking
Pain Point to Notice
The many-to-many relationships (courses ↔ instructors, courses ↔ topics, users ↔ courses)
each require a join table. In MongoDB you might have just stuffed an array of
instructor IDs inside the course document. In PostgreSQL, that's a separate table
with two foreign keys. This feels like more work — and it is — but it also means
you can query "all courses by instructor X" or "all topics for course Y" without
scanning every document. The schema enforces the relationships; the database does
the work. Migrations are the other big shift: you can't just change a model and
restart. You write a migration, run it, and if it's wrong, you roll back. This
discipline pays off when you're working on a production database that has real data.