Skip to main content

ORM Relations

This page shows practical relation definitions for both SQL and Mongo models, including the driver-aware choices you should make in each workflow.

Relation definitions in model schema

Relations are declared in static schema using the relation helper:
relation("hasMany", "Post", { foreignKey: "user_id" })
relation("belongsTo", "User", { foreignKey: "user_id" })
relation("belongsToMany", "Tag", {
  pivotTable: "post_tags",
  pivotLocalKey: "post_id",
  pivotForeignKey: "tag_id",
})
relation("morphOne", "Image", { morphName: "imageable" })
relation("morphMany", "Comment", { morphName: "commentable" })
relation("morphTo", "Commentable", { morphName: "commentable" })
Options are validated at runtime and fed into migration/graph tooling.

Scenario 1: SQL relational model

import { column, relation } from "@alpha.consultings/eloquent-orm.js";
import { SqlModel } from "@alpha.consultings/eloquent-orm.js/Model";

type UserAttrs = { id?: number; name?: string };
type PostAttrs = { id?: number; title?: string; user_id?: number };

export class User extends SqlModel<UserAttrs> {
  static tableName = "users";
  static connectionName = process.env.DB_CONNECTION ?? "sqlite";
  static schema = {
    id: column("increments", undefined, { primary: true }),
    name: column("string", 255),
    posts: relation("hasMany", "Post", { foreignKey: "user_id" }),
  };
  constructor() {
    super("users", process.env.DB_CONNECTION ?? "sqlite");
  }
}

export class Post extends SqlModel<PostAttrs> {
  static tableName = "posts";
  static connectionName = process.env.DB_CONNECTION ?? "sqlite";
  static schema = {
    id: column("increments", undefined, { primary: true }),
    title: column("string", 255),
    user_id: column("int"),
    author: relation("belongsTo", "User", { foreignKey: "user_id" }),
    tags: relation("belongsToMany", "Tag", {
      pivotTable: "post_tags",
      pivotLocalKey: "post_id",
      pivotForeignKey: "tag_id",
    }),
  };
  constructor() {
    super("posts", process.env.DB_CONNECTION ?? "sqlite");
  }
}
Use this model style when you need SQL migrations and relational constraints.

Scenario 2: Mongo model runtime

import { column, relation } from "@alpha.consultings/eloquent-orm.js";
import { MongoModel } from "@alpha.consultings/eloquent-orm.js/Model";

type CommentAttrs = {
  id?: number;
  body?: string;
  commentable_type?: string;
  commentable_id?: number;
};
type PhotoAttrs = {
  id?: number;
  path?: string;
  commentable_id?: number;
  commentable_type?: string;
};

export class Comment extends MongoModel<CommentAttrs> {
  static tableName = "comments";
  static connectionName = "mongo";
  static schema = {
    id: column("increments", undefined, { primary: true }),
    body: column("string", 255),
    commentable_id: column("string"),
    commentable_type: column("string", 255),
    commentable: relation("morphTo", "Image", { morphName: "commentable" }),
  };
  constructor() {
    super("comments", "mongo");
  }
}

export class Photo extends MongoModel<PhotoAttrs> {
  static tableName = "photos";
  static connectionName = "mongo";
  static schema = {
    id: column("increments", undefined, { primary: true }),
    path: column("string", 255),
    imageable_id: column("string"),
    imageable_type: column("string", 255),
    imageable: relation("morphTo", "GeoLocation", { morphName: "imageable" }),
    comments: relation("morphMany", "Comment", { morphName: "commentable" }),
  };
  constructor() {
    super("photos", "mongo");
  }
}
In Mongo flows, join-heavy SQL constraints are replaced by document/reference semantics.

Relation helper compatibility

Relation kindSQLMongo
belongsToYesYes (reference-based)
hasManyYesYes (reference-based)
hasOneYesYes (reference-based)
morphOneYesYes
morphManyYesYes
morphToYesYes
belongsToManyYes (pivot)Prefer document references

Tips

  • Keep relation keys/aliases stable (user_id, post_id, morphName, morphAlias) to protect query compatibility.
  • Keep SQL many-to-many explicit with pivot metadata.
  • Keep Mongo models aligned to document boundaries and avoid SQL-only assumptions.
  • Use static database for physical foreign keys and composite indexes. relation(...) is the runtime relation contract, not the full SQL DDL contract.

Learn more