vdb
An interactive database ERD visualiser for Rails apps. Reads your db/schema.rb, infers foreign-key relationships, and renders a D3 force-directed diagram at any path you choose.
Development use only. Do not mount this in production without authentication.

→ See the example app — a blog schema (users / posts / reviewers / reviews / comments / tags) that shows a real-world vdb integration.
Features
- Auto-parses
db/schema.rb(and optional extra schema files) - Infers FK relationships from
_idcolumn naming andadd_foreign_keystatements - Crow's-foot / single-tick cardinality notation
- Table search, zoom in/out, fit-to-screen, drag-to-rearrange
- Positions persisted to
localStorageper database - Click a table to highlight its relationships
- Optional HTTP Basic Auth
- Zero asset-pipeline dependencies — D3 and Stimulus loaded from CDN
Installation
Add to your Gemfile, inside the development group:
group :development do
gem 'vdb', path: 'vendor/gems/vdb' # local path
# or once published:
# gem 'vdb'
end
Run:
bundle install
Setup
1. Mount the engine
In config/routes.rb:
Rails.application.routes.draw do
# Recommended: scope under a path and/or constrain to development
if Rails.env.development?
mount Vdb::Engine, at: '/dev/erd'
end
# rest of your routes…
end
2. Optional initializer
Create config/initializers/vdb.rb only if you need to change defaults:
# config/initializers/vdb.rb
# Only required in development — skip the initializer entirely in other envs.
return unless Rails.env.development?
Vdb.configure do |c|
# HTTP Basic Auth. Leave username nil to disable.
c.username = ENV.fetch('VDB_USER', nil)
c.password = ENV.fetch('VDB_PASS', nil)
# Additional schema files to expose as tabs (label => path).
# 'primary' auto-resolves to db/schema.rb if path is nil.
c.databases = {
'primary' => nil, # db/schema.rb (default)
'audit' => Rails.root.join('db', 'audit_schema.rb')
}
# Page title shown in the header.
c.title = 'Database ERD'
end
3. Visit the ERD
http://localhost:3000/dev/erd
Configuration reference
| Option | Type | Default | Description |
|---|---|---|---|
username |
`String \ | nil` | nil |
password |
`String \ | nil` | nil |
databases |
`Hash<String, Pathname\ | String\ | nil>` |
title |
String |
'Database ERD' |
Title shown in the browser and page header. |
Routing helpers
Inside the engine:
| Helper | Path |
|---|---|
vdb.root_path |
/dev/erd |
vdb.root_path(database: 'audit') |
/dev/erd?database=audit |
vdb.parse_path |
/dev/erd/parse |
How it works
- Schema parsing —
Vdb::SchemaToGraphreads the schema file line-by-line. It extractscreate_tableblocks (columns + types),add_foreign_keydeclarations, andadd_index … unique: trueconstraints. - FK inference — any
*_idcolumn whose referenced plural table exists in the schema becomes an implicit link (unless an explicitadd_foreign_keyalready covers it). - Cardinality —
many → 1by default. If the FK column has a single-column unique index, it becomes1 → 1. - Rendering — a Stimulus controller drives a D3 force-directed simulation. Node positions are saved to
localStoragekeyed by database name.
Screenshots
All screenshots below are taken from the bundled example app — a Rails 8 blog with users, posts, reviewers, reviews, comments, and tags.
Full ERD — force-directed graph

All seven tables laid out automatically by D3's force simulation. Foreign-key lines carry crow's-foot (N) and single-tick (1) cardinality markers. FK columns are highlighted in cyan.
Table search

Typing in the search box dims non-matching tables so you can quickly locate what you need in a large schema.
Relationship highlighting

Click any table header to highlight its direct neighbours and relationships; everything else fades back.
Example app pages
| Posts list | Post detail |
|---|---|
![]() |
![]() |
Excluded tables
The following Rails-internal and Solid* tables are hidden automatically:
active_storage_*,action_text_rich_textsar_internal_metadata,schema_migrationssolid_cache_*,solid_queue_*,solid_cable_*
Security note
This gem renders your database schema — never mount it in production without authentication. The recommended pattern is to wrap the mount in if Rails.env.development? and optionally add Basic Auth via the config.

