Unit 2 · Module 3
Reading A Schema Like A Map
What an unfamiliar database is trying to tell you, if you know what to look for
A schema is a map, not a list
The default way to look at a database is as a list of tables, one after another. That's how the Supabase dashboard shows them. That's how documentation usually presents them. It's also the worst way to actually understand a system.
A schema is fundamentally spatial. Tables aren't arranged in a list; they're arranged in a web, with lines (foreign keys) connecting them. Some tables sit in the center of that web with lots of connections. Others sit at the edges with only one. The shape of the web tells you what the system is really about.
Analogy: The subway map move
Reading a city as a list of street names tells you almost nothing. Reading it as a subway map tells you everything: which neighborhoods connect, where the transit hubs are, which lines are central, which are spurs.
A schema diagram is the same kind of move. The list of tables is the street index. The diagram with the foreign-key lines drawn between them is the subway map.
Three kinds of tables you'll see in any schema
Once a schema diagram is in front of you, the tables fall into roughly three categories based on how they're connected. Naming them out loud is what makes a schema legible.
Central tables (the hubs)
A central table is one that lots of other tables point at. Users is almost always central — posts have a user_id, comments have a user_id, orders have a user_id, sessions have a user_id. The users table sits in the middle of the diagram with arrows coming in from every direction.
Spotting central tables first is the fastest way to figure out what an app is
about. A schema where orders is the central hub is an
e-commerce app. A schema where messages is central is a
messaging app. A schema where elections is central is a
political app. The hub is the thing the system is actually built around.
Leaf tables (the edges)
A leaf table is one that points at others but isn't pointed at by anything. They sit at the edges of the diagram. Comments point at posts and users, but nothing points at comments — so comments is a leaf. Notifications, audit logs, and webhook events are usually leaves too. They depend on the rest of the system but don't have dependents.
Leaves are usually safer to change than hubs. Adding a column to a leaf table affects almost nothing else. Adding a column to a hub can ripple through every table that points at it.
Join tables (the connectors)
Join tables (from the many-to-many discussion on the relationship-shapes
page) sit between two other tables and exist only to connect them. They have
distinctive names — usually two table names jammed together, like
book_authors, post_tags, group_members.
Their columns are almost always just two foreign keys.
Join tables tell you immediately that the two things they connect have a many-to-many relationship. Spotting one is how you know the system is explicitly modeling that shape.
How to read an unfamiliar schema, in order
Walking into a database you've never seen before, the steps are:
- Find the hub. Which table has the most incoming foreign keys? That's the heart of the app. Whatever it represents is what the system is for.
- Trace the spokes. Which tables point at the hub? Those are the main concepts the app is tracking. Comments, posts, orders, whatever.
- Find the join tables. Names like
users_organizationsortags_posts. Each one is a many-to-many connection between two of the spokes. - Find the leaves. Tables nothing points at. These are usually peripheral concerns — logs, notifications, settings.
- Now read the columns. Only after the shape is clear. Reading columns first is the trap. The shape is what gives them context.
A worked example: a small podcast directory
To make this concrete, imagine a podcast directory app with these tables:
Tables in a podcast directory
users
podcasts
episodes
subscriptions
listens
tags
podcast_tags
notifications Going through the steps:
The hub: Probably podcasts. Episodes belong to
podcasts. Subscriptions point at podcasts. Tags get attached to podcasts.
Multiple tables converge on it.
The spokes: episodes (each episode belongs to
one podcast). users (the other hub — users subscribe, users
listen, users get notifications).
The join tables: podcast_tags is the obvious
one — many-to-many between podcasts and tags. subscriptions is
also functioning as a join table between users and podcasts (a user
subscribes to many podcasts; a podcast has many subscribers), though it has
extra columns like subscribed_at that make it more than just a
pure connector.
The leaves: listens (records of who played
what), notifications. Nothing depends on them. They're the
historical/peripheral data.
Without writing a single query, the shape of the app is already legible: it's a system where users discover podcasts (subscribing, browsing by tags), play episodes (logging listens), and get notified when new episodes drop. All of that came from reading the map, not the SQL.
What schema diagrams do and don't show
Modern schema visualizers — Supabase's built-in one, dbdiagram.io, DataGrip, custom-built schema maps inside admin UIs — actually show quite a lot. Column names, column types, primary key markers, foreign key arrows, nullable vs non-nullable indicators, and unique constraints all typically render right on the diagram. A well-built schema map can also group tables by category (core / billing / analytics / etc.) and color-code them so the structure is even easier to scan.
What diagrams still leave out is the behavior layer:
- Which columns are indexed for query performance (beyond the indexes Postgres creates automatically for primary keys and unique constraints).
- RLS policies and other access rules — who's allowed to read or write which rows.
- Triggers and functions that fire on inserts, updates, or deletes.
- How the data actually flows through application code — which API routes read which tables, where the writes come from, what the lifecycle of a row looks like.
All of that lives in the SQL and the application code, not in the picture. Still, the picture is the right starting place: once you've seen the shape, every behavior-layer detail (the indexes, the policies, the triggers, the API routes) has somewhere to attach. Without the shape, those details float free and you end up looking up the same relationships over and over.
The next page returns to design rather than reading: when faced with a new feature or a new product, how do you decide what gets its own table?