Thuta Learning
AdvancedData & Databasesbeginner

Schema Diagrams and Designing from Access Patterns

What you'll walk away with

  • Explain the core ideas behind Schema Diagrams and Designing from Access Patterns
  • Read the diagram/table and identify the shape of the data model, schema, or architecture
  • Explain how this concept or system choice applies to a real project

Build the mental model

A schema diagram is a map of a database: boxes for tables, a line marking each primary key, and arrows showing which foreign keys point where. Reading one quickly is a core skill.

The learning platform's diagram below shows Users, Courses, Lessons, Enrollments, Progress, and Bookmarks, with every foreign key drawn as an arrow back to the table it references.

  • "Get all lessons for a course, in order" -> needs an index on Lesson.course_id (and a sort by order).
  • "Get a user's progress across a course" -> needs an index on Progress.user_id (or a combined index with course).
  • "Find a course by its URL slug" -> needs a unique index on Course.slug, not just the primary key.
  • "Get a user's bookmarks" -> needs an index on Bookmark.user_id, or every lookup scans the whole table.

The common trap

A schema can be perfectly normalized and still perform badly if it was designed in the abstract and never checked against the patterns real screens and API endpoints actually depend on.

text
LEARNING PLATFORM SCHEMA
------------------------
USERS                     COURSES
+------------+            +-------------+
| PK id      |            | PK id       |
|    email   |            |    slug     |
|    name    |            |    title    |
+------------+            +-------------+
      |  \                      |
      |   \                     |
      |    \                    v
      |     \             LESSONS
      |      \            +-------------+
      |       \           | PK id       |
      |        \--------->| FK course_id|
      |                    |    order    |
      |                    +-------------+
      v                          ^
ENROLLMENTS                      |
+---------------+                |
| PK id         |                |
| FK user_id    |                |
| FK course_id  |          PROGRESS
+---------------+          +---------------+
                            | PK id         |
BOOKMARKS                   | FK user_id    |
+---------------+           | FK lesson_id  |
| PK id         |           +---------------+
| FK user_id    |
| FK lesson_id  |
+---------------+

Connect it to a real scenario

Start from the diagram, but finish from the access patterns. Write out the exact queries the learning platform's screens need — the course page, the dashboard, the course-detail page, the saved-items page.

Each pattern is a small test the schema must pass. Filtering without an index means a full table scan that gets worse as data grows, not better — these are not hypothetical, they are the filters production code runs on every request.

The code example below encodes that test as a function: it takes a candidate schema and a list of access patterns, and reports which ones the schema already answers efficiently, which need an index, and which need a schema change.

Try the working example

javascript
function checkAccessPatterns(accessPatterns, schema) {
  return accessPatterns.map((pattern) => {
    const entitySchema = schema[pattern.entity];
    if (!entitySchema) {
      return { ...pattern, verdict: "schema change needed (entity does not exist)" };
    }
    if (!entitySchema.fields.includes(pattern.filterField)) {
      return { ...pattern, verdict: "schema change needed (field missing)" };
    }
    if (!entitySchema.indexes.includes(pattern.filterField)) {
      return { ...pattern, verdict: "needs an index" };
    }
    return { ...pattern, verdict: "efficient" };
  });
}

const accessPatterns = [
  { name: "Get all lessons for a course", entity: "lessons", filterField: "course_id" },
  { name: "Get a user's progress", entity: "progress", filterField: "user_id" },
  { name: "Find a course by slug", entity: "courses", filterField: "slug" },
  { name: "Get a user's bookmarks", entity: "bookmarks", filterField: "user_id" }
];

const schema = {
  lessons: { fields: ["id", "course_id", "title", "order"], indexes: ["id"] },
  progress: { fields: ["id", "user_id", "lesson_id", "completed_at"], indexes: ["id", "user_id"] },
  courses: { fields: ["id", "slug", "title"], indexes: ["id"] },
  bookmarks: { fields: ["id", "user_id", "lesson_id"], indexes: ["id"] }
};

const results = checkAccessPatterns(accessPatterns, schema);
for (const r of results) {
  console.log(r.name + " -> " + r.verdict);
}
You should see
Checked against the sample schema, three of four access patterns come back "needs an index" (lessons by course_id, courses by slug, bookmarks by user_id all lack an index on that field), while "Get a user's progress" comes back "efficient" because user_id is already indexed on Progress. That mix is intentional: it shows the checker catching real gaps instead of reporting everything as fine.

5-minute try-it

Add a new access pattern, { name: "Find a lesson by its slug within a course", entity: "lessons", filterField: "slug" }, to the accessPatterns array and run the code again. What verdict comes back, and what change to the schema object would fix it?

One important caution

Treating the schema diagram as the finished design instead of a starting map that still has to be checked against real queries.

Normalizing a schema correctly but never listing the app's actual access patterns, so missing indexes only surface once production is slow.

MDN: Structuring related dataHow Databases Work

Easy traps

  • Treating the schema diagram as the finished design instead of a starting map that still has to be checked against real queries.
  • Normalizing a schema correctly but never listing the app's actual access patterns, so missing indexes only surface once production is slow.
  • This course teaches database concepts and the product landscape at a framework-neutral level -- for hands-on SQL syntax or PostgreSQL/MongoDB/Redis depth, continue to the SQL, PostgreSQL, MongoDB, or Redis tutorials.

Exercise

Add a new access pattern, { name: "Find a lesson by its slug within a course", entity: "lessons", filterField: "slug" }, to the accessPatterns array and run the code again. What verdict comes back, and what change to the schema object would fix it?

You'll know it worked when: Checked against the sample schema, three of four access patterns come back "needs an index" (lessons by course_id, courses by slug, bookmarks by user_id all lack an index on that field), while "Get a user's progress" comes back "efficient" because user_id is already indexed on Progress. That mix is intentional: it shows the checker catching real gaps instead of reporting everything as fine.