Files
shiftcraft/.claude/skills/pocketbase-best-practices/rules/query-back-relations.md
2026-04-17 23:26:01 +00:00

4.6 KiB

title, impact, impactDescription, tags
title impact impactDescription tags
Use Back-Relations for Inverse Lookups HIGH Fetch related records without separate queries query, relations, back-relations, expand, inverse

Use Back-Relations for Inverse Lookups

Back-relations allow you to expand records that reference the current record, enabling inverse lookups in a single request. Use the collectionName_via_fieldName syntax.

Incorrect (manual inverse lookup):

// Fetching a user, then their posts separately
async function getUserWithPosts(userId) {
  const user = await pb.collection('users').getOne(userId);

  // Extra request for posts
  const posts = await pb.collection('posts').getList(1, 100, {
    filter: pb.filter('author = {:userId}', { userId })
  });

  return { ...user, posts: posts.items };
}
// 2 API calls

// Fetching a post, then its comments
async function getPostWithComments(postId) {
  const post = await pb.collection('posts').getOne(postId);
  const comments = await pb.collection('comments').getFullList({
    filter: pb.filter('post = {:postId}', { postId }),
    expand: 'author'
  });

  return { ...post, comments };
}
// 2 API calls

Correct (using back-relation expand):

// Expand posts that reference this user
// posts collection has: author (relation to users)
async function getUserWithPosts(userId) {
  const user = await pb.collection('users').getOne(userId, {
    expand: 'posts_via_author'  // collectionName_via_fieldName
  });

  console.log('User:', user.name);
  console.log('Posts:', user.expand?.posts_via_author);
  return user;
}
// 1 API call!

// Expand comments that reference this post
// comments collection has: post (relation to posts)
async function getPostWithComments(postId) {
  const post = await pb.collection('posts').getOne(postId, {
    expand: 'comments_via_post,comments_via_post.author'
  });

  const comments = post.expand?.comments_via_post || [];
  comments.forEach(comment => {
    console.log(`${comment.expand?.author?.name}: ${comment.content}`);
  });

  return post;
}
// 1 API call with nested expansion!

// Multiple back-relations
async function getUserWithAllContent(userId) {
  const user = await pb.collection('users').getOne(userId, {
    expand: 'posts_via_author,comments_via_author,likes_via_user'
  });

  return {
    user,
    posts: user.expand?.posts_via_author || [],
    comments: user.expand?.comments_via_author || [],
    likes: user.expand?.likes_via_user || []
  };
}

Back-relation syntax:

{referencing_collection}_via_{relation_field}

Examples:
- posts_via_author      -> posts where author = current record
- comments_via_post     -> comments where post = current record
- order_items_via_order -> order_items where order = current record
- team_members_via_team -> team_members where team = current record

Nested back-relations:

// Get user with posts and each post's comments
const user = await pb.collection('users').getOne(userId, {
  expand: 'posts_via_author.comments_via_post'
});

// Access nested data
const posts = user.expand?.posts_via_author || [];
posts.forEach(post => {
  console.log('Post:', post.title);
  const comments = post.expand?.comments_via_post || [];
  comments.forEach(c => console.log('  Comment:', c.content));
});

Important considerations:

// Back-relations always return arrays, even if the relation field
// is marked as single (maxSelect: 1)

// Limited to 1000 records per back-relation
// For more, use separate paginated query
const user = await pb.collection('users').getOne(userId, {
  expand: 'posts_via_author'
});
// If user has 1500 posts, only first 1000 are included

// For large datasets, use paginated approach
async function getUserPostsPaginated(userId, page = 1) {
  return pb.collection('posts').getList(page, 50, {
    filter: pb.filter('author = {:userId}', { userId }),
    sort: '-created'
  });
}

Use in list queries:

// Get all users with their post counts
// (Use view collection for actual counts)
const users = await pb.collection('users').getList(1, 20, {
  expand: 'posts_via_author'
});

users.items.forEach(user => {
  const postCount = user.expand?.posts_via_author?.length || 0;
  console.log(`${user.name}: ${postCount} posts`);
});

When to use back-relations vs separate queries:

Scenario Approach
< 1000 related records Back-relation expand
Need pagination Separate query with filter
Need sorting/filtering Separate query
Just need count View collection
Display in list Back-relation (if small)

Reference: PocketBase Back-Relations