Relationships & Population Guide
Complete guide for working with relationships between documents in COCOBASE.
📖 Table of Contents
- Setting Up Relationships
- Basic Population
- Multiple Populations
- Nested Populations
- Filtering by Relationship Fields
- Field Selection with Relationships
- Real-World Examples
Setting Up Relationships
Documents can reference other documents using foreign keys. By convention, use {collection_name}_id format:
Example Document Structure
Posts Collection:
{
"id": "post-1",
"title": "My First Post",
"content": "Hello World",
"author_id": "user-123",
"category_id": "cat-456",
"created_at": "2025-11-01T10:00:00Z"
}
Users Collection:
{
"id": "user-123",
"name": "John Doe",
"email": "john@example.com",
"role": "admin"
}
Basic Population
Use the populate parameter to automatically fetch related documents:
import { buildFilterQuery } from "coco_base_js";
// Populate author relationship
const queryString = buildFilterQuery({
populate: "author",
});
// Use with API
const posts = await fetch(
`${baseUrl}/collections/posts/documents?${queryString}`
);
Response:
[
{
"id": "post-1",
"title": "My First Post",
"author_id": "user-123",
"author": {
"id": "user-123",
"name": "John Doe",
"email": "john@example.com",
"role": "admin"
}
}
]
Multiple Populations
Populate multiple relationships at once:
// Populate both author and category
const queryString = buildFilterQuery({
populate: ["author", "category"],
});
Response:
[
{
"id": "post-1",
"title": "My First Post",
"author_id": "user-123",
"category_id": "cat-456",
"author": {
"id": "user-123",
"name": "John Doe"
},
"category": {
"id": "cat-456",
"name": "Technology"
}
}
]
Alternative syntax:
buildFilterQuery({
populate: "author,category",
});
Nested Populations
Populate relationships within relationships using dot notation:
// Comments -> Post -> Author
const queryString = buildFilterQuery({
populate: "post.author",
});
Response:
[
{
"id": "comment-1",
"text": "Great post!",
"post_id": "post-1",
"post": {
"id": "post-1",
"title": "My First Post",
"author_id": "user-123",
"author": {
"id": "user-123",
"name": "John Doe"
}
}
}
]
Multiple Nested Populations
buildFilterQuery({
populate: ["post.author", "post.category", "user"],
});
Filtering by Relationship Fields
Query documents based on related document data:
// Find posts by admin authors
const queryString = buildFilterQuery({
filters: {
"author.role": "admin",
},
populate: "author",
});
SQL Equivalent:
SELECT posts.* FROM posts
JOIN users ON posts.author_id = users.id
WHERE users.role = 'admin'
Using Operators on Relationships
// Find posts by authors with email containing 'john'
buildFilterQuery({
filters: {
"author.email_contains": "john",
},
populate: "author",
});
// Find posts by authors older than 18
buildFilterQuery({
filters: {
"author.age_gte": 18,
},
populate: "author",
});
Complex Relationship Queries
// Find posts by admin authors OR verified authors in Technology category
buildFilterQuery({
filters: {
"[or]author.role": "admin",
"[or]author.isVerified": true,
"category.name": "Technology",
status: "published",
},
populate: ["author", "category"],
sort: "created_at",
order: "desc",
});
Field Selection with Relationships
Control response size by selecting specific fields:
// Only return title, content, and author name/email
buildFilterQuery({
select: ["title", "content", "author.name", "author.email"],
populate: "author",
});
Response:
[
{
"title": "My First Post",
"content": "Hello World",
"author": {
"name": "John Doe",
"email": "john@example.com"
}
}
]
Select from Multiple Relations
buildFilterQuery({
select: [
"title",
"content",
"author.name",
"author.email",
"category.name",
"category.slug",
],
populate: ["author", "category"],
});
Real-World Examples
Blog System
// Get published posts with author and category info
const blogQuery = buildFilterQuery({
filters: {
status: "published",
"category.slug_ne": "draft",
},
populate: ["author", "category"],
select: [
"title",
"excerpt",
"slug",
"created_at",
"author.name",
"author.avatar",
"category.name",
"category.slug",
],
sort: "created_at",
order: "desc",
limit: 20,
});
const response = await fetch(
`${baseUrl}/collections/posts/documents?${blogQuery}`
);
Social Media Feed
// Get posts from friends with comments
const feedQuery = buildFilterQuery({
filters: {
"author.id_in": friendIds.join(","),
visibility: "public",
"author.isBlocked": false,
},
populate: ["author", "comments.user"],
select: [
"content",
"image",
"created_at",
"likes",
"author.name",
"author.avatar",
],
sort: "created_at",
order: "desc",
limit: 50,
});
E-commerce Orders
// Get user orders with product details
const ordersQuery = buildFilterQuery({
filters: {
"customer.id": userId,
"[or]status": "pending",
"[or]status": "processing",
"product.inStock": true,
},
populate: ["customer", "product", "product.category"],
select: [
"orderNumber",
"quantity",
"total",
"status",
"customer.name",
"product.name",
"product.price",
],
sort: "created_at",
order: "desc",
});
Project Management
// Get tasks with project details
const tasksQuery = buildFilterQuery({
filters: {
"assignee.team_id": teamId,
"[or:priority]priority": "high",
"[or:priority]isOverdue": true,
"project.status_ne": "archived",
status_notin: "completed,cancelled",
},
populate: ["assignee", "project", "project.owner"],
select: [
"title",
"description",
"dueDate",
"priority",
"assignee.name",
"project.name",
],
sort: "dueDate",
order: "asc",
});
Best Practices
✅ DO's
-
Use Consistent Naming Convention
// Good: {collection}_id format
author_id: "user-123";
category_id: "cat-456"; -
Select Only Needed Fields
buildFilterQuery({
select: ["title", "author.name"],
populate: "author",
}); -
Filter Before Populating
buildFilterQuery({
filters: { status: "published" },
populate: "author",
}); -
Limit Nested Populations
// Good: 1-2 levels deep
populate: "post.author";
// Avoid: Very deep nesting
populate: "comment.post.author.team.company";
❌ DON'Ts
-
Don't Over-Populate
// Bad: Populating unnecessary relationships
populate: ["author", "category", "tags", "comments", "likes"];
// Good: Only what you need
populate: ["author", "category"]; -
Don't Skip Pagination
// Always use limit for large result sets
buildFilterQuery({
populate: "author",
limit: 50,
offset: 0,
});
Performance Tips
- Index Foreign Keys - Ensure all
*_idfields are indexed - Use Pagination - Always limit result sets
- Select Specific Fields - Don't fetch unnecessary data
- Cache Relationships - Cache frequently accessed related data
Next Steps
- Advanced Features - Learn more advanced SDK features
- Examples - See complete implementations