Relations

Because data without relationships is just a spreadsheet with commitment issues.


Overview

SeedORM supports defining relationships between models. You declare them as an object in the model definition, then populate related documents at query time with the include option. It's like a JOIN, except you don't have to stare at SQL for 20 minutes trying to figure out which direction the arrow goes.


Defining relations

Add a relations object to your model definition. Each key is the relation name, and the value specifies the relation type, the related model, and the key fields used for linking.

import { SeedORM, FieldType, RelationType } from "seedorm";
const User = db.model({
name: "User",
collection: "users",
schema: {
name: { type: FieldType.String, required: true },
email: { type: FieldType.String, unique: true },
},
relations: {
posts: { type: RelationType.HasMany, model: "Post", foreignKey: "authorId" },
profile: { type: RelationType.HasOne, model: "Profile", foreignKey: "userId" },
roles: { type: RelationType.ManyToMany, model: "Role", joinCollection: "user_roles", foreignKey: "userId", relatedKey: "roleId" },
},
});

Since RelationType is a string enum, you can also use plain strings like "hasMany". They're equivalent.


hasMany

A one-to-many relationship. The foreign key lives on the related model. A User has many Posts, where each Post stores an authorId pointing back to the User. One parent, many children. Classic family dynamics.

// User has many Posts
relations: {
posts: { type: "hasMany", model: "Post", foreignKey: "authorId" },
}
// Post model stores authorId
const Post = db.model({
name: "Post",
collection: "posts",
schema: {
title: { type: FieldType.String, required: true },
authorId: { type: FieldType.String, required: true },
},
relations: {
author: { type: "belongsTo", model: "User", foreignKey: "authorId" },
},
});

hasOne

A one-to-one relationship. Similar to hasMany but returns a single document instead of an array. One user, one profile. Monogamous data.

// User has one Profile
relations: {
profile: { type: "hasOne", model: "Profile", foreignKey: "userId" },
}

belongsTo

The inverse of hasOne/hasMany. The foreign key lives on this model. Use this on the child side of the relationship. The Post knows who wrote it. The User doesn't have to keep track.

// Post belongs to User
relations: {
author: { type: "belongsTo", model: "User", foreignKey: "authorId" },
}
// When queried with include, the related User is attached:
const post = await Post.findById("pos_abc123", {
include: ["author"],
});
// post.author => { id: "usr_...", name: "Alice", ... }

manyToMany

A many-to-many relationship using a join collection. You specify the joinCollection name and the two key fields: foreignKey (this model's ID in the join table) and relatedKey (the related model's ID in the join table). Yes, it's a junction table. No, you don't have to think about it that hard.

// User has many Roles through user_roles
relations: {
roles: {
type: "manyToMany",
model: "Role",
joinCollection: "user_roles",
foreignKey: "userId",
relatedKey: "roleId",
},
}

Populating relations

Use the include option on find, findById, or findOne to populate related documents. Just pass the relation names (the keys from your relations object) and SeedORM fetches everything for you. No N+1 queries to debug at 3am.

// Populate a single relation
const user = await User.findById("usr_abc123", {
include: ["posts"],
});
// user.posts => [{ id: "pos_...", title: "...", authorId: "usr_abc123" }, ...]
// Populate multiple relations
const user = await User.findById("usr_abc123", {
include: ["posts", "profile", "roles"],
});
// Works with find() and findOne() too
const users = await User.find({
filter: { role: { $eq: "admin" } },
include: ["posts"],
});

associate() and dissociate()

For manyToMany relations, use associate() and dissociate() to manage the join table entries. Link things together, unlink them later. Relationship management without the therapy.

// Link a user to a role
await User.associate("usr_abc123", "roles", "rol_editor");
// Remove the link
await User.dissociate("usr_abc123", "roles", "rol_editor");

RelationType enum

SeedORM exports a RelationType enum for type-safe relation definitions. Since it's a string enum, plain strings work too.

import { RelationType } from "seedorm";
RelationType.HasOne // "hasOne"
RelationType.HasMany // "hasMany"
RelationType.BelongsTo // "belongsTo"
RelationType.ManyToMany // "manyToMany"
// Both forms are equivalent:
{ type: RelationType.HasMany, model: "Post", foreignKey: "authorId" }
{ type: "hasMany", model: "Post", foreignKey: "authorId" }