ERD

Every table, and how they hang together

Tables, columns, keys and the relationships between them, read from the schema that actually ships: a Prisma file, a GraphQL SDL, or the entities and migrations themselves. What you keep is a JSON spec in the repo. Re-run the import and the diagram matches the database again.

> show me the data model
wrote db.erd.json · db.erd.html
 
# what the skill ran for you
$ vibex import prisma prisma/schema.prisma docs/db.erd.json
$ vibex render docs/db.erd.json --open

Two real ones

Both come from the Relay showcase, a fictional parcel network. Relay's thirteen tables are split into Booking, Network and Movement. Billing was imported straight from a schema.prisma.

relay.erd.html · 13 tables · 13 relationships · 3 groupsFull screen ↗

Click a table for its columns and relationships. Every table has its own URL, #node=<id>, so you can link someone to exactly one.

/search0fit+-zoomtthemeEscclear

What it draws

Every field in the spec, and where it shows up. Only id and name are required on a table; everything else is drawn when it is there.

TABLES

kind
What the box is.
tableviewenumembedded
An enum draws its values in place of columns.
columns[]
Name and type on every row, with a PK, FK or UQ badge. A nullable column reads name?. default, note and the fk target show when you hover the row. indexed is kept in the spec but not drawn.
fkon a column
The target, written entity.column. Validate refuses one that points at a table the spec does not have.
schema · description
The database schema (public, billing) and one sentence on what the table holds.
sources[]
File and line. With meta.repository set, the table links to that exact line on GitHub or GitLab.

RELATIONSHIPS

from · to
The child (the side holding the foreign key) and the parent it references.
from_cardinality
to_cardinality
Crow's-foot marks at each end, so an optional parent reads differently from a required one.
onezero-or-onemanyone-or-many
identifying
Lines are solid. Set identifying: false and the line is dashed, for a foreign key that is not part of the child's key.
on_delete
What deleting the parent does to the child rows. It shows when you hover the line, beside the cardinality.
cascaderestrictset-nullno-action
label · from_column · to_column
A verb phrase for the line (“booked by”) and the exact columns that join. Validate warns if a named column is not on that table.

LAYOUT

groups[]
Bounded contexts, schemas or modules, drawn as a labelled frame behind their tables. A table can be in one group.
layout.cols · gapX · gapY
Tables go on a grid, ceil(√n) columns wide unless you say otherwise. Lines route at right angles, and lines that share a gap each get their own lane.
row · colon a table
Pin a table to a cell when the automatic placement puts it somewhere unhelpful. No two tables can share a cell.
cards[]
Up to four short notes under the diagram, each with a tone: neutral, info, success, warning or danger.

Where it reads from

Two importers turn a schema into a spec in one pass. Without a schema file, the skill reads the code and writes the spec itself.

$ vibex import prisma prisma/schema.prisma docs/db.erd.json

Models become tables and enums keep their values. @id, @@id, @unique, @@unique and @@map carry over, and an optional field becomes a nullable column. Each @relation becomes a relationship with its join columns and onDelete. The child side is many unless its foreign key column is unique. Every table points back to the line it came from.

The spec you keep

This is the file that goes in the repo and gets reviewed. The HTML is generated from it, and you can throw the HTML away.

{
  "schema_version": 1,
  "diagram_type": "erd",
  "meta": { "title": "Orders" },
  "entities": [
    { "id": "customer", "name": "customers",
      "columns": [
        { "name": "id", "type": "uuid", "pk": true },
        { "name": "email", "type": "text", "unique": true }
      ] },
    { "id": "order", "name": "orders",
      "columns": [
        { "name": "id", "type": "uuid", "pk": true },
        { "name": "customer_id", "type": "uuid",
          "fk": "customer.id", "indexed": true }
      ] }
  ],
  "relationships": [
    { "from": "order", "to": "customer",
      "label": "placed by", "on_delete": "restrict" }
  ]
}

Cardinality defaults to many-to-one, which is what a foreign key usually means, so the relationship above needs no more than its two ends.

IDs are the handles everything else uses. An endpoint lists the table IDs it touches, a docs claim names orders.erd#order as its subject, and a link ending #node=order opens the diagram with that table selected.

The full contract is schemas/erd.schema.json.

What validate stops

Errors are things that would draw a wrong diagram, and they fail the command. Warnings are things a reader would trip on, and they never block a render.

$ vibex validate docs/orders.erd.json
error dangling-fk entities[0].columns[1].fk "customer.id" points to unknown entity "customer"
error dangling-ref relationships[0].to "customer" is not an entity id (known ids: order, status)
warning enum-values entities[1] (order_status) is an enum without values[]
2 error(s), 1 warning(s)

Errors

Exit 1. Each one names the field.

  • dangling-fka column's fk points at a table that is not in the spec
  • dangling-refa relationship or group names an unknown table, and the message lists the ones that exist
  • duplicate-columntwo columns with the same name on one table
  • multi-groupa table placed in two groups
  • duplicate-idtwo tables or relationships sharing an ID

Warnings

Printed, never blocking.

  • emptya table with no columns
  • enum-valuesan enum with no values listed
  • unknown-columna relationship's from_column or to_column is not on that table
  • sizemore than 25 tables in one diagram. Split it by group

The sentences it writes

A docs section can ask for the erd.entities and erd.relationships generators. Each turns the spec into plain English, so the prose about the schema cannot disagree with the diagram next to it. These two came out of the Relay build word for word:

The shipments table has 7 columns and is keyed on id. reference may be null.erd.entities · relay.erd#shipment
Each shipments row relates to exactly one merchants row. Each merchants row relates to any number of shipments rows. The link is shipments.merchant_id → merchants.id. A merchants row cannot be deleted while shipments rows reference it. The relationship is labelled “booked by”.erd.relationships · relay.erd

What it connects to

ENDPOINTS

From a table to the routes that touch it

List table IDs in an endpoint's entities and the dashboard shows, for every table, which endpoints read or write it.

Endpoints →
DASHBOARD

Next to everything else

Drop the spec in a folder with the others and it becomes one entry in a single file, with the entity-to-endpoint links worked out.

Dashboard →
CHANGELOG

Which commit moved it

With --specs, release notes say which commits touched this diagram.

Changelog →