Edit Page

Document Relationships

🔧 Configuration

Sets localhost:8080 with admin:secret
Values are saved in your browser

MongoDB is a document database without built-in join operations, but that doesn’t mean your data can’t have relationships. RESTHeart provides a convenient way to define and navigate relationships between documents, making it easier to model connected data.

Introduction to MongoDB Relationships

In MongoDB, documents can reference other documents in several ways:

  • Embedded documents - nesting related data within a single document

  • Document references - storing the ID of a related document as a field value

  • Array of references - storing multiple related document IDs in an array

RESTHeart enhances MongoDB’s reference approach by allowing you to declare existing relationships in your collections. Once declared, RESTHeart automatically adds hyperlinks to related documents in the representation.

Important

Relationship links are only available in the HAL representation format. To see these links, add the query parameters ?rep=HAL&hal=f to your requests.

Defining Relationships

Relationships are defined in the collection metadata using the rels property. This is an array of relationship objects that tell RESTHeart how documents in the collection are connected to other documents.

Relationship Definition Object

{
    "rel": "relationshipName",
    "type": "RELATIONSHIP_TYPE",
    "role": "ROLE_TYPE",
    "target-db": "targetDatabase",
    "target-coll": "targetCollection",
    "ref-field": "referenceField"
}
Property Description Required

rel

The name of the relationship (used in the _links property)

Yes

type

Relationship type: ONE_TO_ONE, MANY_TO_ONE, ONE_TO_MANY, or MANY_TO_MANY

Yes

role

OWNING (reference field is in this document) or INVERSE (reference field is in the target document)

Yes

target-db

Database containing the referenced documents

No (defaults to current database)

target-coll

Collection containing the referenced documents

Yes

ref-field

Field containing the reference ID(s) or JSON path expression

Yes

Understanding Relationship Types

  • ONE_TO_ONE - A document references exactly one other document (e.g., a person and their passport)

  • MANY_TO_ONE - Many documents reference the same document (e.g., employees and their department)

  • ONE_TO_MANY - A document references many other documents (e.g., a customer and their orders)

  • MANY_TO_MANY - Many documents reference many other documents (e.g., students and courses)

Understanding Relationship Roles

  • OWNING - The current document contains the reference field

  • INVERSE - The target document contains the reference field

Example Relationship Patterns

Pattern 1: Document Tree (Parent-Child)

This pattern allows you to create hierarchical data where each document references its parent.

  1. Create a collection with a parent relationship:

cURL

curl -i -X PUT [INSTANCE-URL]/categories \
  -H "Authorization: Basic [BASIC-AUTH]" \
  -H "Content-Type: application/json" \
  -d '{"rels": [{"rel": "parent", "type": "MANY_TO_ONE", "role": "OWNING", "target-coll": "categories", "ref-field": "parentId"}], "description": "Product categories organized in a tree"}'

HTTPie

echo '{"rels": [{"rel": "parent", "type": "MANY_TO_ONE", "role": "OWNING", "target-coll": "categories", "ref-field": "parentId"}], "description": "Product categories organized in a tree"}' | \
http PUT [INSTANCE-URL]/categories \
  Authorization:"Basic [BASIC-AUTH]" \
  Content-Type:application/json

JavaScript

fetch('[INSTANCE-URL]/categories', {
  method: 'PUT',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    rels: [{
      rel: 'parent',
      type: 'MANY_TO_ONE',
      role: 'OWNING',
      'target-coll': 'categories',
      'ref-field': 'parentId'
    }],
    description: 'Product categories organized in a tree'
  })
})
.then(response => {
  if (response.ok) {
    console.log('Write request executed successfully');
  } else {
    console.error('Write request failed:', response.status);
  }
})
.catch(error => console.error('Error:', error));
  1. Create the root category:

cURL

curl -i -X PUT [INSTANCE-URL]/categories/electronics \
  -H "Authorization: Basic [BASIC-AUTH]" \
  -H "Content-Type: application/json" \
  -d '{"name": "Electronics", "description": "Electronic devices and accessories"}'

HTTPie

echo '{"name": "Electronics", "description": "Electronic devices and accessories"}' | \
http PUT [INSTANCE-URL]/categories/electronics \
  Authorization:"Basic [BASIC-AUTH]" \
  Content-Type:application/json

JavaScript

fetch('[INSTANCE-URL]/categories/electronics', {
  method: 'PUT',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'Electronics',
    description: 'Electronic devices and accessories'
  })
})
.then(response => {
  if (response.ok) {
    console.log('Write request executed successfully');
  } else {
    console.error('Write request failed:', response.status);
  }
})
.catch(error => console.error('Error:', error));
  1. Create child categories referencing the parent:

cURL

curl -i -X PUT [INSTANCE-URL]/categories/smartphones \
  -H "Authorization: Basic [BASIC-AUTH]" \
  -H "Content-Type: application/json" \
  -d '{"name": "Smartphones", "description": "Mobile phones with advanced features", "parentId": "electronics"}'

HTTPie

echo '{"name": "Smartphones", "description": "Mobile phones with advanced features", "parentId": "electronics"}' | \
http PUT [INSTANCE-URL]/categories/smartphones \
  Authorization:"Basic [BASIC-AUTH]" \
  Content-Type:application/json

JavaScript

fetch('[INSTANCE-URL]/categories/smartphones', {
  method: 'PUT',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'Smartphones',
    description: 'Mobile phones with advanced features',
    parentId: 'electronics'
  })
})
.then(response => {
  if (response.ok) {
    console.log('Write request executed successfully');
  } else {
    console.error('Write request failed:', response.status);
  }
})
.catch(error => console.error('Error:', error));

cURL

curl -i -X PUT [INSTANCE-URL]/categories/laptops \
  -H "Authorization: Basic [BASIC-AUTH]" \
  -H "Content-Type: application/json" \
  -d '{"name": "Laptops", "description": "Portable computers", "parentId": "electronics"}'

HTTPie

echo '{"name": "Laptops", "description": "Portable computers", "parentId": "electronics"}' | \
http PUT [INSTANCE-URL]/categories/laptops \
  Authorization:"Basic [BASIC-AUTH]" \
  Content-Type:application/json

JavaScript

fetch('[INSTANCE-URL]/categories/laptops', {
  method: 'PUT',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'Laptops',
    description: 'Portable computers',
    parentId: 'electronics'
  })
})
.then(response => {
  if (response.ok) {
    console.log('Write request executed successfully');
  } else {
    console.error('Write request failed:', response.status);
  }
})
.catch(error => console.error('Error:', error));
  1. Now when you request a child document with HAL representation:

cURL

curl -i -X GET [INSTANCE-URL]/categories/smartphones?rep=HAL&hal=f \
  -H "Authorization: Basic [BASIC-AUTH]"

HTTPie

http GET [INSTANCE-URL]/categories/smartphones \
  Authorization:"Basic [BASIC-AUTH]" \
  rep==HAL \
  hal==f

JavaScript

fetch('[INSTANCE-URL]/categories/smartphones?rep=HAL&hal=f', {
  method: 'GET',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]'
  }
})
.then(response => response.json())
.then(data => {
  console.log('Retrieved data:', data);
})
.catch(error => console.error('Error:', error));

+ You’ll get a response with a link to the parent:

+

{
    "_id": "smartphones",
    "name": "Smartphones",
    "description": "Mobile phones with advanced features",
    "parentId": "electronics",
    "_links": {
        "self": {
            "href": "/categories/smartphones"
        },
        "parent": {
            "href": "/categories/electronics"
        }
    }
}

Pattern 2: One-to-Many, Owner Side

In this pattern, a document owns references to multiple related documents. For example, a band owning references to its albums.

  1. Create the albums collection:

cURL

curl -i -X PUT [INSTANCE-URL]/albums \
  -H "Authorization: Basic [BASIC-AUTH]" \
  -H "Content-Type: application/json" \
  -d '{"description": "Music albums"}'

HTTPie

echo '{"description": "Music albums"}' | \
http PUT [INSTANCE-URL]/albums \
  Authorization:"Basic [BASIC-AUTH]" \
  Content-Type:application/json

JavaScript

fetch('[INSTANCE-URL]/albums', {
  method: 'PUT',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    description: 'Music albums'
  })
})
.then(response => {
  if (response.ok) {
    console.log('Write request executed successfully');
  } else {
    console.error('Write request failed:', response.status);
  }
})
.catch(error => console.error('Error:', error));
  1. Create the bands collection with a relationship to albums:

cURL

curl -i -X PUT [INSTANCE-URL]/bands \
  -H "Authorization: Basic [BASIC-AUTH]" \
  -H "Content-Type: application/json" \
  -d '{"rels": [{"rel": "albums", "type": "ONE_TO_MANY", "role": "OWNING", "target-coll": "albums", "ref-field": "albumIds"}], "description": "Music bands and artists"}'

HTTPie

echo '{"rels": [{"rel": "albums", "type": "ONE_TO_MANY", "role": "OWNING", "target-coll": "albums", "ref-field": "albumIds"}], "description": "Music bands and artists"}' | \
http PUT [INSTANCE-URL]/bands \
  Authorization:"Basic [BASIC-AUTH]" \
  Content-Type:application/json

JavaScript

fetch('[INSTANCE-URL]/bands', {
  method: 'PUT',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    rels: [{
      rel: 'albums',
      type: 'ONE_TO_MANY',
      role: 'OWNING',
      'target-coll': 'albums',
      'ref-field': 'albumIds'
    }],
    description: 'Music bands and artists'
  })
})
.then(response => {
  if (response.ok) {
    console.log('Write request executed successfully');
  } else {
    console.error('Write request failed:', response.status);
  }
})
.catch(error => console.error('Error:', error));
  1. Create some albums:

cURL

curl -i -X PUT [INSTANCE-URL]/albums/album1 \
  -H "Authorization: Basic [BASIC-AUTH]" \
  -H "Content-Type: application/json" \
  -d '{"title": "The Dark Side of the Moon", "year": 1973}'

HTTPie

echo '{"title": "The Dark Side of the Moon", "year": 1973}' | \
http PUT [INSTANCE-URL]/albums/album1 \
  Authorization:"Basic [BASIC-AUTH]" \
  Content-Type:application/json

JavaScript

fetch('[INSTANCE-URL]/albums/album1', {
  method: 'PUT',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    title: 'The Dark Side of the Moon',
    year: 1973
  })
})
.then(response => {
  if (response.ok) {
    console.log('Write request executed successfully');
  } else {
    console.error('Write request failed:', response.status);
  }
})
.catch(error => console.error('Error:', error));

cURL

curl -i -X PUT [INSTANCE-URL]/albums/album2 \
  -H "Authorization: Basic [BASIC-AUTH]" \
  -H "Content-Type: application/json" \
  -d '{"title": "Wish You Were Here", "year": 1975}'

HTTPie

echo '{"title": "Wish You Were Here", "year": 1975}' | \
http PUT [INSTANCE-URL]/albums/album2 \
  Authorization:"Basic [BASIC-AUTH]" \
  Content-Type:application/json

JavaScript

fetch('[INSTANCE-URL]/albums/album2', {
  method: 'PUT',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    title: 'Wish You Were Here',
    year: 1975
  })
})
.then(response => {
  if (response.ok) {
    console.log('Write request executed successfully');
  } else {
    console.error('Write request failed:', response.status);
  }
})
.catch(error => console.error('Error:', error));
  1. Create a band that references these albums:

cURL

curl -i -X PUT [INSTANCE-URL]/bands/pinkfloyd \
  -H "Authorization: Basic [BASIC-AUTH]" \
  -H "Content-Type: application/json" \
  -d '{"name": "Pink Floyd", "formed": 1965, "albumIds": ["album1", "album2"]}'

HTTPie

echo '{"name": "Pink Floyd", "formed": 1965, "albumIds": ["album1", "album2"]}' | \
http PUT [INSTANCE-URL]/bands/pinkfloyd \
  Authorization:"Basic [BASIC-AUTH]" \
  Content-Type:application/json

JavaScript

fetch('[INSTANCE-URL]/bands/pinkfloyd', {
  method: 'PUT',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'Pink Floyd',
    formed: 1965,
    albumIds: ['album1', 'album2']
  })
})
.then(response => {
  if (response.ok) {
    console.log('Write request executed successfully');
  } else {
    console.error('Write request failed:', response.status);
  }
})
.catch(error => console.error('Error:', error));
  1. When you request the band document with HAL representation:

cURL

curl -i -X GET [INSTANCE-URL]/bands/pinkfloyd?rep=HAL&hal=f \
  -H "Authorization: Basic [BASIC-AUTH]"

HTTPie

http GET [INSTANCE-URL]/bands/pinkfloyd \
  Authorization:"Basic [BASIC-AUTH]" \
  rep==HAL \
  hal==f

JavaScript

fetch('[INSTANCE-URL]/bands/pinkfloyd?rep=HAL&hal=f', {
  method: 'GET',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]'
  }
})
.then(response => response.json())
.then(data => {
  console.log('Retrieved data:', data);
})
.catch(error => console.error('Error:', error));

+ You’ll get a response with a link to query the albums:

+

{
    "_id": "pinkfloyd",
    "name": "Pink Floyd",
    "formed": 1965,
    "albumIds": ["album1", "album2"],
    "_links": {
        "self": {
            "href": "/bands/pinkfloyd"
        },
        "albums": {
            "href": "/albums?filter={'_id':{'$in':['album1','album2']}}"
        }
    }
}

Pattern 3: One-to-Many, Inverse Side

In this pattern, multiple documents reference a single document. For example, albums referencing their band.

  1. Create the bands collection:

cURL

curl -i -X PUT [INSTANCE-URL]/bands \
  -H "Authorization: Basic [BASIC-AUTH]" \
  -H "Content-Type: application/json" \
  -d '{"rels": [{"rel": "albums", "type": "ONE_TO_MANY", "role": "INVERSE", "target-coll": "albums", "ref-field": "bandId"}], "description": "Music bands and artists"}'

HTTPie

echo '{"rels": [{"rel": "albums", "type": "ONE_TO_MANY", "role": "INVERSE", "target-coll": "albums", "ref-field": "bandId"}], "description": "Music bands and artists"}' | \
http PUT [INSTANCE-URL]/bands \
  Authorization:"Basic [BASIC-AUTH]" \
  Content-Type:application/json

JavaScript

fetch('[INSTANCE-URL]/bands', {
  method: 'PUT',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    rels: [{
      rel: 'albums',
      type: 'ONE_TO_MANY',
      role: 'INVERSE',
      'target-coll': 'albums',
      'ref-field': 'bandId'
    }],
    description: 'Music bands and artists'
  })
})
.then(response => {
  if (response.ok) {
    console.log('Write request executed successfully');
  } else {
    console.error('Write request failed:', response.status);
  }
})
.catch(error => console.error('Error:', error));
  1. Create the albums collection:

cURL

curl -i -X PUT [INSTANCE-URL]/albums \
  -H "Authorization: Basic [BASIC-AUTH]" \
  -H "Content-Type: application/json" \
  -d '{"description": "Music albums with band references"}'

HTTPie

echo '{"description": "Music albums with band references"}' | \
http PUT [INSTANCE-URL]/albums \
  Authorization:"Basic [BASIC-AUTH]" \
  Content-Type:application/json

JavaScript

fetch('[INSTANCE-URL]/albums', {
  method: 'PUT',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    description: 'Music albums with band references'
  })
})
.then(response => {
  if (response.ok) {
    console.log('Write request executed successfully');
  } else {
    console.error('Write request failed:', response.status);
  }
})
.catch(error => console.error('Error:', error));
  1. Create a band:

cURL

curl -i -X PUT [INSTANCE-URL]/bands/beatles \
  -H "Authorization: Basic [BASIC-AUTH]" \
  -H "Content-Type: application/json" \
  -d '{"name": "The Beatles", "formed": 1960}'

HTTPie

echo '{"name": "The Beatles", "formed": 1960}' | \
http PUT [INSTANCE-URL]/bands/beatles \
  Authorization:"Basic [BASIC-AUTH]" \
  Content-Type:application/json

JavaScript

fetch('[INSTANCE-URL]/bands/beatles', {
  method: 'PUT',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    name: 'The Beatles',
    formed: 1960
  })
})
.then(response => {
  if (response.ok) {
    console.log('Write request executed successfully');
  } else {
    console.error('Write request failed:', response.status);
  }
})
.catch(error => console.error('Error:', error));
  1. Create albums that reference the band:

cURL

curl -i -X PUT [INSTANCE-URL]/albums/abbey-road \
  -H "Authorization: Basic [BASIC-AUTH]" \
  -H "Content-Type: application/json" \
  -d '{"title": "Abbey Road", "year": 1969, "bandId": "beatles"}'

HTTPie

echo '{"title": "Abbey Road", "year": 1969, "bandId": "beatles"}' | \
http PUT [INSTANCE-URL]/albums/abbey-road \
  Authorization:"Basic [BASIC-AUTH]" \
  Content-Type:application/json

JavaScript

fetch('[INSTANCE-URL]/albums/abbey-road', {
  method: 'PUT',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    title: 'Abbey Road',
    year: 1969,
    bandId: 'beatles'
  })
})
.then(response => {
  if (response.ok) {
    console.log('Write request executed successfully');
  } else {
    console.error('Write request failed:', response.status);
  }
})
.catch(error => console.error('Error:', error));

cURL

curl -i -X PUT [INSTANCE-URL]/albums/revolver \
  -H "Authorization: Basic [BASIC-AUTH]" \
  -H "Content-Type: application/json" \
  -d '{"title": "Revolver", "year": 1966, "bandId": "beatles"}'

HTTPie

echo '{"title": "Revolver", "year": 1966, "bandId": "beatles"}' | \
http PUT [INSTANCE-URL]/albums/revolver \
  Authorization:"Basic [BASIC-AUTH]" \
  Content-Type:application/json

JavaScript

fetch('[INSTANCE-URL]/albums/revolver', {
  method: 'PUT',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    title: 'Revolver',
    year: 1966,
    bandId: 'beatles'
  })
})
.then(response => {
  if (response.ok) {
    console.log('Write request executed successfully');
  } else {
    console.error('Write request failed:', response.status);
  }
})
.catch(error => console.error('Error:', error));
  1. When you request the band document with HAL representation:

cURL

curl -i -X GET [INSTANCE-URL]/bands/beatles?rep=HAL&hal=f \
  -H "Authorization: Basic [BASIC-AUTH]"

HTTPie

http GET [INSTANCE-URL]/bands/beatles \
  Authorization:"Basic [BASIC-AUTH]" \
  rep==HAL \
  hal==f

JavaScript

fetch('[INSTANCE-URL]/bands/beatles?rep=HAL&hal=f', {
  method: 'GET',
  headers: {
    'Authorization': 'Basic [BASIC-AUTH]'
  }
})
.then(response => response.json())
.then(data => {
  console.log('Retrieved data:', data);
})
.catch(error => console.error('Error:', error));

+ You’ll get a response with a link to query all the band’s albums:

+

{
    "_id": "beatles",
    "name": "The Beatles",
    "formed": 1960,
    "_links": {
        "self": {
            "href": "/bands/beatles"
        },
        "albums": {
            "href": "/albums?filter={'bandId':'beatles'}"
        }
    }
}

Advanced Relationship Features

Reference Fields with JSON Path Expressions

For references stored in nested documents, use a JSON path expression starting with $:

{
    "rel": "author",
    "type": "MANY_TO_ONE",
    "role": "OWNING",
    "target-coll": "users",
    "ref-field": "$.metadata.authorId"
}

This will match a document structure like:

{
    "title": "My Article",
    "content": "...",
    "metadata": {
        "authorId": "user123",
        "publishDate": "2023-05-15"
    }
}

Cross-Database Relationships

To reference documents in another database, specify the target-db property:

{
    "rel": "products",
    "type": "ONE_TO_MANY",
    "role": "OWNING",
    "target-db": "inventory",
    "target-coll": "products",
    "ref-field": "productIds"
}

Best Practices

  1. Choose the right relationship type - Consider the cardinality of your data relationships carefully

  2. Use descriptive relationship names - Name relationships in a way that clearly describes their purpose

  3. Keep consistency - For bidirectional relationships, ensure that both sides are properly defined

  4. Consider performance - For very large collections, be mindful of the performance impact of relationships

  5. Document your schema - Keep documentation of your data model including all relationships

Limitations

  1. Relationships are metadata only - they don’t enforce referential integrity

  2. Links are only available in HAL representation format

  3. Following multiple relationship links requires multiple requests