Adding Relationships
This continues from Your First Project. We’ll add comments and tags to the blog, introducing foreign keys and many-to-many relationships.
Add a comments table
Section titled “Add a comments table”alab table blog commentEdit schemas/blog/comment.js:
export default table({ id: col.id(), body: col.body(), author: col.belongs_to("auth.user"), post: col.belongs_to("blog.post").on_delete("cascade"),}) .timestamps();Two foreign keys here:
author→auth_user.id— default referential action (restrict).post→blog_post.idwithon_delete("cascade")— when a post is deleted, its comments are deleted too.
Each belongs_to creates an _id column (author_id, post_id) and an
index automatically.
Add a tags table with many-to-many
Section titled “Add a tags table with many-to-many”alab table blog tagEdit schemas/blog/tag.js:
export default table({ id: col.id(), name: col.name().unique(), slug: col.slug(),});Now add the many-to-many relationship. Open schemas/blog/post.js and chain
.many_to_many():
export default table({ id: col.id(), title: col.title(), slug: col.slug(), body: col.body(), status: col.enum(["draft", "published", "archived"]).default("draft"), author: col.belongs_to("auth.user"),}) .timestamps() .soft_delete() .many_to_many("blog.tag");This tells alab to auto-create a join table (blog_post_blog_tag) with
post_id and tag_id foreign keys, a unique composite index, and individual
indexes on each column.
Optional relationships
Section titled “Optional relationships”Sometimes a foreign key should allow NULL. For example, a post might
optionally have a reviewer:
// Add to schemas/blog/post.js columnsreviewer: col.belongs_to("auth.user").optional().on_delete("set_null"),.optional()makes the column nullable..on_delete("set_null")sets the FK toNULLwhen the referenced user is deleted, instead of blocking the delete.
Generate the migration
Section titled “Generate the migration”alab new add_comments_and_tagsalab diffs your updated schemas against the state after 001_initial_schema
and generates 002_add_comments_and_tags.js. The migration will:
- Create
blog_commentwith foreign keys toauth_userandblog_post - Create
blog_tag - Create the
blog_post_blog_tagjoin table - Create all indexes
Preview and apply:
alab migrate --dryalab migrateWhat you have now
Section titled “What you have now”auth_user ├── id, email, username, password, is_active ├── created_at, updated_at │blog_post ├── id, title, slug, body, status, author_id ├── created_at, updated_at, deleted_at ├── FK → auth_user │blog_comment ├── id, body, author_id, post_id ├── created_at, updated_at ├── FK → auth_user ├── FK → blog_post (cascade delete) │blog_tag ├── id, name, slug │blog_post_blog_tag (join table) ├── post_id, tag_id ├── FK → blog_post ├── FK → blog_tagFour tables, two migrations, zero hand-written SQL. The schema files remain the single source of truth.