Model and read relationships
Relationships connect focused tables without duplicating their data. TaruviBase derives relationship metadata from the schema's foreign keys.
The patterns below use flat tables. Whole-table JSONB storage has a separate relationship compatibility boundary.
| Shape | Example | Model |
|---|---|---|
| Belongs to | A task belongs to one project | tasks.project_id references projects.id |
| Has many | A project has many tasks | Reverse view of tasks.project_id |
| Many to many | A task can have many tags | A junction table references both tables |
Define a belongs-to relationship#
Create the referenced table first. Then add the foreign-key field and
foreignKeys definition to the table that owns the reference. For example,
add project_id to tasks and reference projects.id.
Use the logical table name in reference.resource. NO ACTION keeps deletion
explicit: remove or redirect referencing tasks before deleting their project.
Use the foreign-key declaration in the schema
reference for
the exact descriptor and supported delete actions.
Model many-to-many data#
Create a junction table when both sides can have many records. For tasks and
tags, define task_tags with:
- its own stable
id; task_idreferencingtasks.id; andtag_idreferencingtags.id.
Create, query, and delete junction records through the ordinary record
workflows. This makes each relationship explicit and gives it room for fields
such as created_at or assigned_by.
Include related records#
Add populate to a list or read to return related records with each result.
Use the foreign-key field for a belongs-to relationship, such as
populate=project_id on tasks, or the related table's name for the reverse
direction, such as populate=tasks on projects. populate=* includes every
first-level relationship. In the SDKs, use .populate(['project_id']) in
JavaScript or .populate("project_id") in Python.
populate can't be combined with aggregation queries.
TaruviBase checks the caller's access to the table you read, not to the related
tables that populate expands. Related records come back without their own
table's access policy being applied. When a related table holds data some
callers mustn't see, don't populate it: read that table in its own request, as
below, so its policy applies.
Read related data#
To page through related records on their own, or to combine data your own way, read each table in its own request:
- Read the root records from their table.
- Collect the foreign-key values needed by the application.
- Query the related table with the same site, app, and caller context.
- Join the records in application code.
This keeps response size, pagination, and authorization visible at each step.
For example, after reading tasks and collecting their project_id values into
projectIds, fetch the related projects with one bounded query:
- JavaScript SDK
- Python SDK
- Refine
- REST API
await database
.from('projects')
.filters('id', 'in', projectIds)
.sort('name', 'asc')
.sort('id', 'asc')
.page(1)
.pageSize(20)
.execute();
(
client.database
.from_("projects")
.filter("id", "in", project_ids)
.sort("name", "asc")
.sort("id", "asc")
.page(1)
.page_size(20)
.execute()
)
useList({
resource: 'projects',
filters: [{field: 'id', operator: 'in', value: projectIds}],
sorters: [
{field: 'name', order: 'asc'},
{field: 'id', order: 'asc'},
],
pagination: {currentPage: 1, pageSize: 20, mode: 'server'},
});
/api/apps/$TARUVI_APP_SLUG/datatables/projects/data/Headers
AuthorizationApi-Key $TARUVI_API_KEY
Query parameters
id__in$PROJECT_IDSorderingname,idpage1page_size20
View cURL
curl -G "$TARUVI_SITE_URL/api/apps/$TARUVI_APP_SLUG/datatables/projects/data/" \
-H "Authorization: Api-Key $TARUVI_API_KEY" \
--data-urlencode "id__in=$PROJECT_IDS" \
--data-urlencode 'ordering=name,id' \
--data-urlencode 'page=1' \
--data-urlencode 'page_size=20'
200Returns the related projects in their own app-scoped collection response.
Set PROJECT_IDS to the comma-separated project UUIDs collected from the task response.
Build a map keyed by project ID, then attach each project to its task in
application code. Apply the same pattern in the other direction by filtering
tasks.project_id with the selected project IDs.
Choose a query pattern#
| Requirement | Pattern |
|---|---|
| Read a foreign-key value | Read the root record normally |
| Read the referenced record | Query the referenced table by ID |
| Read a has-many collection | Filter the child table by its foreign-key field |
| Read a many-to-many collection | Query the junction table, then query the target IDs |
| Filter or sort by related data | Query the related IDs first, then filter and sort the root table |
| Show related rows with root rows | Use populate, or read both tables and join them in application code |
Separate table reads give you independent pagination and response size for each table.
Relationship checklist#
- Create the referenced table before the referencing table.
- Use compatible field types on both sides.
- Choose delete behavior deliberately.
- Test valid and missing references.
- Apply pagination and stable ordering to each collection read.
- Use
populatefor small related sets, and separate paged reads for large ones.
Next, use aggregations to summarize the records returned by a table query.