Document relationships
Relationships establish associations between documents.
Define document relationships
A collection schema can include a document-type-definitions, document type definition that controls what fields are accepted in a collection’s documents.
The document type can include field definition that accept a collection’s documents as a data type:
collection Product {
...
// `store` accepts `Store` collection documents.
store: Ref<Store>
...
}
A field can accept documents as part of an array:
collection Order {
...
// `cart` accepts an array of `Product` collection documents.
cart: Array<Ref<Product>>
...
}
Or as part of a nested object:
collection Customer {
...
preferences: {
// `store` accepts `Store` collection documents
// as part of the `preferences` object.
store: Ref<Store>,
emailList: Boolean,
...
}
...
}
Create a document relationship
You create relationships by including a reference to a document in another document. Fauna supports the following relationship types:
Relationship type | Example |
---|---|
A book has one author. |
|
An author writes many books. |
|
A reader has many books, and a book has many readers. |
Create a one-to-one relationship
A one-to-one relationship exists when a document in one collection has only one associated document in another collection.
In the following example, each product is associated with one store.
// Get a store
let store = Store.byName("DC Fruits").first()
// Create a product that references the store
Product.create({
name: "limes",
description: "Organic, 1 ct",
store: store
// Return the product `name`, `description`, and resolved `store`
}) { name, description, store }
Create a one-to-many relationship
A one-to-many relationship exists when a document in a collection is associated with one or more documents in the another collection.
For example, one store may be associated with multiple products.
// Get a store
let store = Store.byName("DC Fruits").first()
// Define multiple products that reference the store
let products = [
{ name: "limes", description: "Organic, 2 ct", store: store },
{ name: "limes", description: "Organic, 3 ct", store: store }
]
// Create each product
products.map(product => Product.create({
product
// Return the product `name`, `description`, and resolved `store`
})) { name, description, store }
Create many-to-many relationships
A many-to-many relationship exists when a document in one collection is associated with multiple other documents in another collection, and the reverse.
For example, a customer can order several products. A product can also be ordered by several customers.
In Fauna, creating a many-to-many relationship requires a third collection to track the associations.
In the following example, the Order
collection tracks relationships
between Product
and Customer
documents.
// Define customer data
let customerData = [
{ firstName: "Dan", lastName: "Doe" },
{ firstName: "Evie", lastName: "Ewin" },
]
// Define product data
let productData = [
{ name: "key limes", description: "Conventional, 2 ct" },
{ name: "key limes", description: "Conventional, 3 ct" },
]
// Create customers and products
let customers = customerData.map(data => Customer.create(data))
let products = productData.map(data => Product.create(data))
// Create orders for each customer and product
customers.flatMap(customer =>
products.map(product =>
Order.create({
customer: customer,
cart: [{ product: product }]
})
)
// Return the resolved `customer` and `cart` for each order
) { customer, cart }
Traverse document relationships with projection
When you include a document in another document’s field, Fauna stores the value as a reference. By default, this reference is returned instead of the document itself.
For example, the following Product
document’s store
field contains a
another document:
{
id: "394062089570746432",
coll: Product,
...
name: "pizza",
description: "Frozen Cheese",
...
store: Store("394062089566552128"),
...
}
Use projection to traverse the
reference and return the store
document:
// Use projection to return `name`, `desciption`, and `store` fields
Product.byName("pizza").first() {
name,
description,
store {
id,
name,
address
}
}
Response with the resolved store
reference:
{
name: "pizza",
description: "Frozen Cheese",
store: {
id: "394062089566552128",
name: "Foggy Bottom Market",
address: {
street: "4 Florida Ave",
city: "Washington",
state: "DC",
zipCode: "20037"
}
}
}
Query by document relationship
You can use an index to query documents by their relationships. Using an index can significantly improve query performance and reduce costs, especially for large datasets.
You define an index as part of a collection schema:
collection Product {
...
store: Ref<Store>
...
// Defines the `byStore()` index.
// Use the index to get `Product` collection
// documents by `store` value. In this case,
// `store` contains `Store` collection documents.
index byStore {
terms [.store]
}
}
Use the index in a projection to query Product
documents
that reference Store
documents in the store
field:
// Get a `Store` collection document.
let store = Store.byName("DC Fruits").first()
store {
id,
name,
// Use the `byStore()` index to get all products for the store.
products: Product.byStore(store) {
id,
name,
description,
}
}
{
id: "401783355920613449",
name: "DC Fruits",
products: {
data: [
{
id: "401783355930050633",
name: "avocados",
description: "Conventional Hass, 4ct bag"
},
{
id: "401783355932147785",
name: "limes",
description: "Conventional, 1 ct"
},
{
id: "401783355934244937",
name: "limes",
description: "Organic, 16 oz bag"
},
{
id: "401783355937390665",
name: "cilantro",
description: "Organic, 1 bunch"
}
]
}
}
See Indexes |
---|
Simplify projections with computed fields
You can call an index in a computed field to simplify projections in queries that traverse document relationships.
Extending the previous example, you can add a computed field to the Store
collection schema:
collection Store {
...
// Defines the `all_products` computed field.
// The field calls the `Product` collection's
// `byStore()` index.
compute all_products = ((store) => Product.byStore(store))
}
Update the previous query’s projection to use the computed field in place
of the byStore()
index call:
// Get a `Store` collection document.
let store = Store.byName("DC Fruits").first()
store {
id,
name,
// Project the `all_product` computed field instead of
// directly calling the `byStore()` index.
all_products {
id,
name,
description,
}
}
{
id: "401783355920613449",
name: "DC Fruits",
products: {
data: [
{
id: "401783355930050633",
name: "avocados",
description: "Conventional Hass, 4ct bag"
},
{
id: "401783355932147785",
name: "limes",
description: "Conventional, 1 ct"
},
{
id: "401783355934244937",
name: "limes",
description: "Organic, 16 oz bag"
},
{
id: "401783355937390665",
name: "cilantro",
description: "Organic, 1 bunch"
}
]
}
}
See Computed field definitions |
---|
Query document relationships in an array
Use mva(<field>)
to index document relationships in an Array field:
collection Order {
...
cart: Array<Ref<Product>>
...
// Defines the `byProduct()` index.
// Use the index to get `Order` collection
// documents by `cart` value. In this case,
// `cart` contains an array of `Product` collection documents.
index byProduct {
terms [mva(.cart)]
}
}
Use the index in a projection to query Order
documents
that reference Product
documents in the cart
field:
// Get a `Product` collection document.
let product = Product.byName("avocados").first()
// Use projection to return `id`, `name`, and `orders` fields
// for the `Product` collection document.
product {
id,
name,
// Use the `byProduct()` index to get all orders
// that contain the product in the `cart` field.
orders: Order.byProduct(product) {
id,
status,
cart {
id,
name,
description
}
}
}
{
id: "401783355930050633",
name: "avocados",
orders: {
data: [
{
id: "401783355955216457",
status: "processing",
cart: [
{
id: "401783355930050633",
name: "avocados",
description: "Conventional Hass, 4ct bag"
},
{
id: "401783355937390665",
name: "cilantro",
description: "Organic, 1 bunch"
},
{
id: "401783355932147785",
name: "limes",
description: "Conventional, 1 ct"
}
]
}
]
}
}
Don’t index document IDs
When indexing a field that contains document, index the entire field, not the document ID:
collection Product {
...
// The `store` field accepts `Store` collection documents.
store: Ref<Store>
...
// Correct:
index byStore {
terms [.store]
}
// Incorrect:
// Indexing `store.id` fails.
// Instead, index by the entire `store`field.
index byStoreId {
terms [.store.id]
}
}
IDs aren’t persistable and can’t be indexed.
Is this article helpful?
Tell Fauna how the article can be improved:
Visit Fauna's forums
or email docs@fauna.com
Thank you for your feedback!