SlintORM
Docsnpm

Relation Traversal

SlintORM provides three methods for filtering across relations without writing manual JOIN SQL. All three read from your @relation annotations to generate queries automatically.

throughRelation(path)

Takes a dot-separated path of relation names and auto-generates INNER JOINs from schema metadata. You still apply the final .where() on the joined table column.

// .throughRelation(path)
// Dot-separated path of relation names
// Auto-generates JOINs from schema metadata — no manual ON clauses

// Get assessments taken by a specific student
// Path: enrollments -> assessments (via enrollment relation)
const assessments = await Assessment.query()
  .throughRelation('enrollment.student')
  .where('students.id', '=', 42)
  .get();

// SlintORM reads your @relation annotations and builds:
// INNER JOIN enrollments ON enrollments.assessmentId = assessments.id
// INNER JOIN students ON students.id = enrollments.studentId

whereRelated(path, column, value)

Combines throughRelation with an IN-subquery resolution. Cleaner than building joins manually for filter-by-relation patterns.

// .whereRelated(path, column, value)
// Combines throughRelation with an IN-subquery resolution
// Cleaner than building joins manually for filter-by-relation patterns

const assessments = await Assessment.query()
  .whereRelated('enrollment.student', 'id', 42)
  .get();
// WHERE assessments.id IN (
//   SELECT assessmentId FROM enrollments
//   WHERE studentId IN (SELECT id FROM students WHERE id = 42)
// )

relatedTo(targetModelName, column, value)

The most declarative option. Uses BFS to automatically discover the shortest relation path between the current model and the named target model. You don't need to know the path.

// .relatedTo(targetModelName, column, value)
// BFS — automatically discovers the shortest path between
// the current model and the target model through the relation graph

// No need to know the path — SlintORM finds it
const assessments = await Assessment.query()
  .relatedTo('Student', 'id', 42)
  .get();

// SlintORM discovers: Assessment -> Enrollment -> Student
// and builds the appropriate subquery chain

Side-by-side comparison

// All three methods solving the same problem:
// "Get assessments for student with id = 42"

// 1. throughRelation — explicit path, generates JOINs
const a = await Assessment.query()
  .throughRelation('enrollment.student')
  .where('students.id', '=', 42)
  .get();

// 2. whereRelated — explicit path, generates IN subquery
const b = await Assessment.query()
  .whereRelated('enrollment.student', 'id', 42)
  .get();

// 3. relatedTo — BFS path discovery, most declarative
const c = await Assessment.query()
  .relatedTo('Student', 'id', 42)
  .get();

Real-world examples

// relatedTo in a real-world scenario:
// Find all posts by users who belong to a given team

const teamPosts = await Post.query()
  .relatedTo('Team', 'id', teamId)
  .where('published', '=', true)
  .orderBy('createdAt', 'DESC')
  .get();

// SlintORM BFS finds: Post -> User -> team_members -> Team
// and builds the appropriate JOIN or subquery chain

// whereRelated for the same query with explicit path:
const teamPosts2 = await Post.query()
  .whereRelated('user.teams', 'id', teamId)
  .where('published', '=', true)
  .get();
When to use which

Use relatedTo when you want maximum simplicity and the relation graph is well-defined. Use whereRelated when you need an explicit path (e.g. two possible paths exist). Use throughRelation when you need the joined table columns accessible in where() or select().

PreviousSubqueriesNextScopes & ExtendedQueryBuilder