Relationships (Beta)
Relationships describe how documents in different collections are associated, so that related documents can be read, updated, or deleted together. Entities in real-life often associate with each other in an organic and logical way, like a person and their dog, an album and its songs, or friends in a social network. These types of association between entities can be modeled in Appwrite using relationships.
Beta Feature
Appwrite Relationships is a beta feature. The API and behavior are subject to change in future versions.
When to Use a Relationship
Relationships help reduce redundant information. For example, a user can create many posts in your app. You can model this without relationships by keeping a copy of the user's information in all the documents representing posts, but this creates a lot of duplicate information in your database about the user.
Benefits of Relationships
Duplicated records waste storage, but more importantly, makes the database much harder to maintain. If the user changes their user name, you will have to update dozens or hundreds of records, a problem commonly known as an update anomaly in databases. You can avoid duplicate information by storing users and posts in separate collections and relating a user and their posts through a relationship.
Tradeoff
Consider using relationships when the same information is found in multiple places to avoid duplicates. However, relationships come with the tradeoff of slowing down queries. For applications where the best read and write performance is important, it may be acceptable to tolerate duplicate data.
Configurable Options
Directionality
Appwrite relationships can be one-way or two-way.
Type | Description |
---|---|
One-way | The relationship is only visible to one side of the relation. This is similar to a tree data structure, where a tree has a reference to all of its leaves, but each leaf does not have a reference to its tree root. |
Two-way | The relationship is visible to both sides of the relationship. This is similar to a graph data structure, where each node has references to all its edges, and all its edges can reference the nodes it connects. |
Types
Appwrite provides four different relationship types to enforce different associative rules between documents.
Type | Description |
---|---|
One-to-one | A document can only be related to one and only one document. If you try to relate multiple documents in a one-to-one relationship, Appwrite throws an error. For example, one user has one profile. |
One-to-many | A document can be related to many other documents. For example, one user can create many posts. |
Many-to-one | Many documents can be related to a single document. For example, many posts can share one author. |
Many-to-many | A document can be related to many other documents. For example, a user can have many friends, and many users can share the same friend. |
On-Delete Behavior
Appwrite also allows you to define the behavior of a relationship when a document is deleted.
Type | Description |
---|---|
Restrict | If a document has at least one related document, it cannot be deleted. |
Cascade | If a document has related documents, when it is deleted, the related documents are also deleted. |
Set null | If a document has related documents, when it is deleted, the related documents are kept with their relationship attribute set to null. |
Relationship Attributes
Relationships are represented in a collection using relationship attributes. The relationship attribute contains the ID of related documents, which it references during read, update, and delete operations. This attribute is null if a document has no related documents.
Creating Relationships in the Appwrite Console
You can create relationships in the Appwrite Console by adding a relationship attribute to a collection.
- In your project, navigate to Databases > Select your database > Select your collection > Attributes > Create attribute.
- Select Relationship as the attribute type.
- In the Relationship modal, select the relationship type and pick the related collection and attributes.
- Pick relationship attribute key(s) to represent the related collection. Relationship attribute keys are used to reference the related collection in queries, so pick something that's intuitive and easy to remember.
- Select desired on delete behavior.
- Click the Create button to create the relationship.
Creating Relationships Programmatically
-
Node.js
const { Client, Databases } = require('node-appwrite'); const client = new Client() .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint .setProject('[PROJECT_ID]'); // Your project ID const databases = new Databases(client); databases.createRelationshipAttribute( 'marvel', // Database ID 'movies', // Collection ID 'reviews', // Related collection ID 'oneToMany', // Relationship type true, // Is two-way 'reviews', // Attribute key 'movie', // Two-way attribute key 'cascade' // On delete action );
-
PHP
use \Appwrite\Client; use \Appwrite\Services\Databases; $client = (new Client()) ->setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint ->setProject('[PROJECT_ID]'); // Your project ID $databases = new Databases($client); $databases->createRelationshipAttribute( databaseId: 'marvel', // Database ID collectionId: 'movies', // Collection ID relatedCollectionId: 'reviews', // Related collection ID type: 'oneToMany', // Relationship type twoWay: true, // Is two-way key: 'reviews', // Attribute key twoWayKey: 'movie', // Two-way attribute key onDelete: 'cascade' // On delete action );
-
Python
from appwrite.client import Client from appwrite.services.databases import Databases client = (Client() .set_endpoint('https://cloud.appwrite.io/v1') # Your API Endpoint .set_project('[PROJECT_ID]')) # Your project ID databases = Databases(client) databases.create_relationship_attribute( database_id='marvel', # Database ID collection_id='movies', # Collection ID related_collection_id='reviews', # Related collection ID type='oneToMany', # Relationship type two_way=True, # Is two-way key='reviews', # Attribute key two_way_key='movie', # Two-way attribute key on_delete='cascade' # On delete action )
-
Ruby
require 'appwrite' include Appwrite client = Client.new .set_endpoint('https://cloud.appwrite.io/v1')# Your API Endpoint .set_project('[PROJECT_ID]') # Your project ID databases = Databases.new(client) databases.create_relationship_attribute( database_id: 'marvel', # Database ID collection_id: 'movies', # Collection ID related_collection_id: 'reviews', # Related collection ID type: 'oneToMany', # Relationship type two_way: true, # Is two-way key: 'reviews', # Attribute key two_way_key: 'movie', # Two-way attribute key on_delete: 'cascade' # On delete action )
-
Deno
import { Client, Databases } from "https://deno.land/x/appwrite/mod.ts"; const client = new Client() .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint .setProject("[PROJECT_ID]"); // Your project ID const databases = new Databases(client); databases.createRelationshipAttribute( "marvel", // Database ID "movies", // Collection ID "reviews", // Related collection ID "oneToMany", // Relationship type true, // Is two-way "reviews", // Attribute key "movie", // Two-way attribute key "cascade" // On delete action );
-
Dart
import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint .setProject('[PROJECT_ID]'); // Your project ID final databases = Databases(client); await databases.createRelationshipAttribute( databaseId: 'marvel', // Database ID collectionId: 'movies', // Collection ID relatedCollectionId: 'reviews', // Related collection ID type: 'oneToMany', // Relationship type twoWay: true, // Is two-way key: 'reviews', // Attribute key twoWayKey: 'movie', // Two-way attribute key onDelete: 'cascade', // On delete action );
-
Kotlin
import io.appwrite.Client import io.appwrite.services.Databases val client = Client() .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint .setProject("[PROJECT_ID]") // Your project ID val databases = Databases(client) databases.createRelationshipAttribute( databaseId = "marvel", // Database ID collectionId = "movies", // Collection ID relatedCollectionId = "reviews", // Related collection ID type = "oneToMany", // Relationship type twoWay = true, // Is two-way key = "reviews", // Attribute key twoWayKey = "movie", // Two-way attribute key onDelete = "cascade" // On delete action )
-
Swift
import Appwrite let client = Client() .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint .setProject("[PROJECT_ID]") // Your project ID let databases = Databases(client) databases.createRelationshipAttribute( databaseId: "marvel", // Database ID collectionId: "movies", // Collection ID relatedCollectionId: "reviews", // Related collection ID type: "oneToMany", // Relationship type twoWay: true, // Is two-way key: "reviews", // Attribute key twoWayKey: "movie", // Two-way attribute key onDelete: "cascade" // On delete action )
The above example adds a relationship between the collections movies and reviews. A relationship attribute with the key reviews
is added to the movies collection and another relationship attribute with the key movie
is added to the reviews collection.
Creating Documents
If a collection has relationship attributes, you can create documents in two ways.
Creating Child Documents
You can create both the parent and child at once in a relationship by nesting data.
-
Web
const { Client, Databases, ID } = require('node-appwrite'); const client = new Client() .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint .setProject('[PROJECT_ID]'); // Your project ID const databases = new Databases(client); await databases.createDocument( 'marvel', 'movies', ID.unique(), { title: 'Spiderman', year: 2002, reviews: [ { author: 'Bob', text: 'Great movie!' }, { author: 'Alice', text: 'Loved it!' } ] } )
-
Flutter
import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint .setProject('[PROJECT_ID]'); // Your project ID final databases = Databases(client); await databases.createDocument( databaseId: 'marvel', collectionId: 'movies', documentId: ID.unique(), data: { 'title': 'Spiderman', 'year': 2002, 'reviews': [ { 'author': 'Bob', 'text': 'Great movie!' }, { 'author': 'Alice', 'text': 'Loved it!' } ] }, );
-
Apple
import Appwrite let client = Client() .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint .setProject("[PROJECT_ID]") // Your project ID let databases = Database(client: client) databases.createDocument( databaseId: "marvel", collectionId: "movies", documentId: ID.unique(), data: [ "title": "Spiderman", "year": 2002, "reviews": [ [ "author": "Bob", "text": "Great movie!" ], [ "author": "Alice", "text": "Loved it!" ] ] ] )
-
Android
import io.appwrite.Client import io.appwrite.services.Database import io.appwrite.ID val client = Client() .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint .setProject("[PROJECT_ID]") // Your project ID val databases = Database(client) databases.createDocument( databaseId = "marvel", collectionId = "movies", documentId = ID.unique(), data = mapOf( "title" to "Spiderman", "year" to 2002, "reviews" to listOf( mapOf("author" to "Bob", "text" to "Great movie!"), mapOf("author" to "Alice", "text" to "Loved it!") ) ) )
If a nested child document is included and no child document ID is provided, the child document will be given a unique ID.
If a nested child document is included and no conflicting child document ID exists, the child document will be created.
If a nested child document is included and the child document ID already exists, the child document will be updated.
Reference Child Documents
If the child documents are already present in the related collection, you can create the parent and reference the child documents using their IDs.
-
Web
const { Client, Databases, ID } = require('node-appwrite'); const client = new Client() .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint .setProject('[PROJECT_ID]'); // Your project ID const databases = new Databases(client); await databases.createDocument( 'marvel', 'movies', ID.unique(), { title: 'Spiderman', year: 2002, reviews: [ 'review1', 'review2' ] } )
-
Flutter
import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint .setProject('[PROJECT_ID]'); // Your project ID final databases = Databases(client); await databases.createDocument( databaseId: 'marvel', collectionId: 'movies', documentId: ID.unique(), data: { 'title': 'Spiderman', 'year': 2002, 'reviews': [ 'review1', 'review2' ] }, );
-
Apple
import Appwrite let client = Client() .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint .setProject("[PROJECT_ID]") // Your project ID let databases = Database(client: client) databases.createDocument( databaseId: "marvel", collectionId: "movies", documentId: ID.unique(), data: [ "title": "Spiderman", "year": 2002, "reviews": [ "review1", "review2" ] ] )
-
Android
import io.appwrite.Client import io.appwrite.services.Database import io.appwrite.ID val client = Client() .setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint .setProject("[PROJECT_ID]") // Your project ID val databases = Database(client) databases.createDocument( databaseId = "marvel", collectionId = "movies", documentId = ID.unique(), data = mapOf( "title" to "Spiderman", "year" to 2002, "reviews" to listOf( "review1", "review2" ) ) )
Querying
Querying is currently not available in the beta version of Appwrite Relationships but will be added in a later version.
Updating Relationships
Relationships can be updated by updating the relationship attribute.
-
Web
const { Client, Databases } = require('node-appwrite'); const client = new Client() .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint .setProject('[PROJECT_ID]'); // Your project ID const databases = new Databases(client); await databases.updateDocument( 'marvel', 'movies', 'spiderman', { title: 'Spiderman', year: 2002, reviews: [ 'review4', 'review5' ] } );
-
Flutter
import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject('[PROJECT_ID]'); final databases = Databases(client); await databases.updateDocument( databaseId: 'marvel', collectionId: 'movies', documentId: 'spiderman', data: { 'title': 'Spiderman', 'year': 2002, 'reviews': [ 'review4', 'review5' ] }, );
-
Apple
import Appwrite let client = Client() .setEndpoint("https://cloud.appwrite.io/v1") .setProject("[PROJECT_ID]") let databases = Database(client: client) databases.updateDocument( databaseId: "marvel", collectionId: "movies", documentId: "spiderman", data: [ "title": "Spiderman", "year": 2002, "reviews": [ "review4", "review5" ] ] )
-
Android
import io.appwrite.Client import io.appwrite.services.Database val client = Client() .setEndpoint("https://cloud.appwrite.io/v1") .setProject("[PROJECT_ID]") val databases = Database(client) databases.updateDocument( databaseId = "marvel", collectionId = "movies", documentId = "spiderman", data = mapOf( "title" to "Spiderman", "year" to 2002, "reviews" to listOf( "review4", "review5" ) ) )
Deleting Relationships
Unlink Relationships, Retain Documents
If you need to unlink documents in a relationship but retain the documents, you can do this by updating the relationship attribute and removing the ID of the related document.
If a document can be related to only one document, you can delete the relationship by setting the relationship attribute to null
.
If a document can be related to more than one document, you can delete the relationship by setting the relationship attribute to an empty list.
Deleting Relationships and Documents
If you need to delete the documents as well as unlink the relationship, the approach depends on the on-delete behavior of a relationship.
If the on-delete behavior is restrict, the link between the documents needs to be deleted first before the documents can be deleted individually.
If the on-delete behavior is set null, deleting a document will leave related documents in place with their relationship attribute set to null. If you wish to also delete related documents, they must be deleted individually.
If the on-delete behavior is cascade, deleting the parent documents also deletes related child documents, except for many-to-one relationships. In many-to-one relationships, there are multiple parent documents related to a single child document, and when the child document is deleted, the parents are deleted in cascade.
-
Web
const { Client, Databases } = require('node-appwrite'); const client = new Client() .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint .setProject('[PROJECT_ID]'); // Your project ID const databases = new Databases(client); await databases.deleteDocument( 'marvel', 'movies', 'spiderman' );
-
Flutter
import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject('[PROJECT_ID]'); final databases = Databases(client); await databases.deleteDocument( databaseId: 'marvel', collectionId: 'movies', documentId: 'spiderman' );
-
Apple
import Appwrite let client = Client() .setEndpoint("https://cloud.appwrite.io/v1") .setProject("[PROJECT_ID]") let databases = Database(client: client) databases.deleteDocument( databaseId: "marvel", collectionId: "movies", documentId: "spiderman" )
-
Android
import io.appwrite.Client import io.appwrite.services.Database val client = Client() .setEndpoint("https://cloud.appwrite.io/v1") .setProject("[PROJECT_ID]") val databases = Database(client) databases.deleteDocument( databaseId = "marvel", collectionId = "movies", documentId = "spiderman" )
Permissions
To access documents in a relationship, you must have permission to access both the parent and child documents.
When creating both the parent and child documents, the child document will inherit permissions from its parent.
You can also provide explicit permissions to the child document if they should be different from their parent.
-
Web
const { Client, Databases, ID } = require('node-appwrite'); const client = new Client() .setEndpoint('https://cloud.appwrite.io/v1') // Your API Endpoint .setProject('[PROJECT_ID]'); // Your project ID const databases = new Databases(client); await databases.createDocument( 'marvel', 'movies', ID.unique(), { title: 'Spiderman', year: 2002, reviews: [ { author: 'Bob', text: 'Great movie!', $permissions: [ Permission.read(Role.any()) ] }, ] } )
-
Flutter
import 'package:appwrite/appwrite.dart'; final client = Client() .setEndpoint('https://cloud.appwrite.io/v1') .setProject('[PROJECT_ID]'); final databases = Databases(client); await databases.createDocument( databaseId: 'marvel', collectionId: 'movies', documentId: ID.unique(), data: { 'title': 'Spiderman', 'year': 2002, 'reviews': [ { 'author': 'Bob', 'text:' 'Great movie!', '\$permissions': [ Permission.read(Role.any()) ] }, ] }, );
-
Apple
import Appwrite let client = Client() .setEndpoint("https://cloud.appwrite.io/v1") .setProject("[PROJECT_ID]") let databases = Database(client: client) databases.createDocument( databaseId: "marvel", collectionId: "movies", documentId: ID.unique(), data: [ "title": "Spiderman", "year": 2002, "reviews": [ [ "author": "Bob", "text": "Great movie!", "$permissions": [ Permission.read(Role.any()) ] ], ] ] )
-
Android
import Appwrite let client = Client() .setEndpoint("https://cloud.appwrite.io/v1") .setProject("[PROJECT_ID]") let databases = Database(client: client) databases.createDocument( databaseId: "marvel", collectionId: "movies", documentId: ID.unique(), data: [ "title": "Spiderman", "year": 2002, "reviews": [ [ "author": "Bob", "text": "Great movie!", "$permissions": [ Permission.read(Role.any()) ] ], ] ] )
When creating, updating, or deleting in a relationship, you must have permission to access all documents referenced. If the user does not have read permission to any document, an exception will be thrown.
Limitations
Relationships can be nested between collections, but are restricted to a max depth of three levels. Relationship attribute key, type, and directionality can't be updated. On-delete behavior is the only option that can be updated for relationship attributes.