Initial commit
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
---
|
||||
title: Use Back-Relations for Inverse Lookups
|
||||
impact: HIGH
|
||||
impactDescription: Fetch related records without separate queries
|
||||
tags: 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):**
|
||||
|
||||
```javascript
|
||||
// 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):**
|
||||
|
||||
```javascript
|
||||
// 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:**
|
||||
|
||||
```javascript
|
||||
// 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:**
|
||||
|
||||
```javascript
|
||||
// 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:**
|
||||
|
||||
```javascript
|
||||
// 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](https://pocketbase.io/docs/working-with-relations/#back-relation-expand)
|
||||
Reference in New Issue
Block a user