Appearance
Relations & Schema Management
Overview
Relations define how collections are linked to each other. ODP supports:
- Many-to-One (M2O) — A field on collection A references a single item in collection B.
- One-to-Many (O2M) — Collection B has many items pointing back to collection A (virtual, uses M2O field on B).
- Many-to-Many (M2M) — Uses a junction table with two M2O fields.
Relations are managed by RelationsService (src/services/relations.ts).
Data Model
Table: odp_relations
| Column | Type | Description |
|---|---|---|
id | integer | Primary key |
many_collection | varchar | Collection with the FK column |
many_field | varchar | FK column name |
one_collection | varchar | Collection being referenced |
one_field | varchar | Field on one_collection that exposes the O2M (nullable for pure M2O) |
one_collection_field | varchar | Field used as the PK on one_collection (usually id) |
one_allowed_collections | json | For polymorphic relations |
junction_field | varchar | Second FK in M2M junction table |
sort_field | varchar | Sort field on the junction table |
one_deselect_action | varchar | What happens to related items on deselect: nullify or delete |
Relation Types
Many-to-One (M2O)
The most common relation. A field in collection A stores the UUID of an item from collection B.
articles.author_id → odp_users.idRow in odp_relations:
json
{
"many_collection": "articles",
"many_field": "author_id",
"one_collection": "odp_users",
"one_field": null
}One-to-Many (O2M)
Virtual relation — no extra DB row needed beyond the M2O. The one_field on the referenced collection exposes the reverse list.
odp_users.articles → articles.author_idRow in odp_relations:
json
{
"many_collection": "articles",
"many_field": "author_id",
"one_collection": "odp_users",
"one_field": "articles"
}Many-to-Many (M2M)
Uses a junction table. Both ends of the M2M are M2O relations going through the junction.
articles ←→ articles_tags ←→ tagsRow in odp_relations:
json
{
"many_collection": "articles_tags",
"many_field": "article_id",
"one_collection": "articles",
"one_field": "tags",
"junction_field": "tag_id"
}Relation Endpoints
POST /relations
Create a relation.
Auth required: Yes (admin for schema changes)
Request Body (M2O)
json
{
"many_collection": "comments",
"many_field": "article_id",
"one_collection": "articles",
"one_field": "comments"
}Request Body (M2M)
json
{
"many_collection": "articles_categories",
"many_field": "article_id",
"one_collection": "articles",
"one_field": "categories",
"junction_field": "category_id"
}Response 200 — Returns the created relation object.
GET /relations
Read all relations across all collections.
Auth required: Yes
Response 200
json
{
"data": [
{
"id": 1,
"many_collection": "articles",
"many_field": "author_id",
"one_collection": "odp_users",
"one_field": null,
"one_collection_field": "id",
"junction_field": null,
"sort_field": null,
"one_deselect_action": "nullify"
}
]
}GET /relations/:collection
Read all relations for a specific collection.
Auth required: Yes
Response 200 — Filtered subset of relations where many_collection or one_collection matches.
PATCH /relations/:collection/:field
Update a relation.
Auth required: Yes
Request Body — Partial update:
json
{
"one_deselect_action": "delete"
}DELETE /relations/:collection/:field
Delete a relation (removes the metadata row, not the DB column).
Auth required: Yes
Response 204
Schema Management
The schema endpoint provides a complete overview of the current database schema.
GET /schema/snapshot
Get a full snapshot of the current schema (all collections, fields, and relations).
Auth required: Admin
Response 200
json
{
"data": {
"collections": [...],
"fields": [...],
"relations": [...]
}
}POST /schema/diff
Compare the current schema against a provided snapshot and return the differences.
Auth required: Admin
Request Body — Full or partial schema snapshot object.
Response 200
json
{
"data": {
"collections": { "added": [...], "removed": [...], "changed": [...] },
"fields": { "added": [...], "removed": [...], "changed": [...] },
"relations": { "added": [...], "removed": [...], "changed": [...] }
}
}POST /schema/apply
Apply a schema snapshot (migrate the database to match the snapshot).
Auth required: Admin
Request Body — Full schema snapshot object (from GET /schema/snapshot or diff output).
Response 204 (no body)
Using Relations in Queries
Relations enable deep queries with the fields and deep parameters.
Read article with nested author and tags:
bash
GET /items/articles/uuid?fields=*,author_id.first_name,author_id.email,tags.tag_id.*Nested filtering (deep):
bash
GET /items/articles?deep[tags][_filter][tag_id][name][_eq]=typescriptAggregate relation counts:
bash
GET /items/articles?fields=id,title&aggregate[count]=tags.*Business Logic Notes
one_deselect_action: When an M2O/M2M relation is cleared:nullify— Sets the FK to NULL (default)delete— Deletes the related item (cascade delete)
- Schema cache invalidation: Creating/updating/deleting relations invalidates the schema cache. Subsequent requests will reload the schema from the database.
- Polymorphic relations: Use
one_allowed_collectionsto define which collections can be referenced (for dynamic relations likeany-type fields).