Relationships

Learn how to define and use relationships between resources in api.vision to model complex data connections.

Understanding Relationships

Relationships define how different resources connect to each other. api.vision supports all common relationship types:

One-to-One

A resource has exactly one related resource, and vice versa.

Example: A user has one profile, and a profile belongs to one user.

One-to-Many

A resource has multiple related resources, but each related resource belongs to only one parent.

Example: A user has many posts, but each post belongs to one user.

Many-to-Many

A resource can have multiple related resources, and vice versa.

Example: A post can have many tags, and each tag can be applied to many posts.

Self-Referential

A resource has relationships with other resources of the same type.

Example: A user can follow other users, creating a self-referential many-to-many relationship.

Defining Relationships

In api.vision, relationships are defined in your resource specifications using special field attributes:

One-to-One Relationship

// One-to-one: User has one Profile
{
  "resources": {
    "users": {
      "fields": {
        "id": { "type": "id" },
        "username": { "type": "string" },
        "email": { "type": "string" },
        "profile": { "type": "relation", "resource": "profiles", "relation": "hasOne" }
      }
    },
    "profiles": {
      "fields": {
        "id": { "type": "id" },
        "userId": { "type": "integer", "reference": "users.id" },
        "bio": { "type": "string" },
        "avatar": { "type": "string" },
        "user": { "type": "relation", "resource": "users", "relation": "belongsTo" }
      }
    }
  }
}

One-to-Many Relationship

// One-to-many: User has many Posts
{
  "resources": {
    "users": {
      "fields": {
        "id": { "type": "id" },
        "username": { "type": "string" },
        "email": { "type": "string" },
        "posts": { "type": "relation", "resource": "posts", "relation": "hasMany" }
      }
    },
    "posts": {
      "fields": {
        "id": { "type": "id" },
        "title": { "type": "string" },
        "content": { "type": "string" },
        "userId": { "type": "integer", "reference": "users.id" },
        "author": { "type": "relation", "resource": "users", "relation": "belongsTo" }
      }
    }
  }
}

Many-to-Many Relationship

// Many-to-many: Posts have many Tags (and vice versa)
{
  "resources": {
    "posts": {
      "fields": {
        "id": { "type": "id" },
        "title": { "type": "string" },
        "content": { "type": "string" },
        "tags": { "type": "relation", "resource": "tags", "relation": "belongsToMany", "through": "post_tags" }
      }
    },
    "tags": {
      "fields": {
        "id": { "type": "id" },
        "name": { "type": "string" },
        "posts": { "type": "relation", "resource": "posts", "relation": "belongsToMany", "through": "post_tags" }
      }
    },
    "post_tags": {
      "fields": {
        "id": { "type": "id" },
        "postId": { "type": "integer", "reference": "posts.id" },
        "tagId": { "type": "integer", "reference": "tags.id" }
      }
    }
  }
}

Relationship Types

api.vision supports the following relationship types:

Relation TypeDescriptionExample
hasOneA one-to-one relationship where the resource has one related resourceA user has one profile
belongsToA one-to-one or many-to-one relationship where the resource belongs to one related resourceA profile belongs to one user
hasManyA one-to-many relationship where the resource has many related resourcesA user has many posts
belongsToManyA many-to-many relationship where the resource belongs to many related resourcesA post belongs to many tags

Working with Relationships

Once relationships are defined, you can use them in various ways:

Expanded Responses

Use the expand query parameter to include related resources in responses:

GET /posts?expand=author,tags

Nested Expansions

Expand multiple levels of relationships with dot notation:

GET /posts?expand=author.profile,comments.author

Filtering on Related Resources

Filter resources based on properties of related resources:

GET /posts?author.role=admin

Creating Resources with Relationships

When creating a resource, you can define its relationships:

// Create a post with author and tags
POST /posts
{
  "title": "Getting Started with API Design",
  "content": "...",
  "authorId": 42,
  "tags": [1, 5, 8]
}

Updating Relationships

Update a resource's relationships with PATCH requests:

// Update a post's tags
PATCH /posts/1
{
  "tags": [2, 5, 9]
}

Advanced Relationship Features

Custom Keys

You can specify custom foreign keys for relationships:

"author": {
  "type": "relation",
  "resource": "users",
  "relation": "belongsTo",
  "foreignKey": "writer_id" // Custom foreign key
}

Pivot Data in Many-to-Many

Access pivot table data in many-to-many relationships:

// Definition
"tags": {
  "type": "relation",
  "resource": "tags",
  "relation": "belongsToMany",
  "through": "post_tags",
  "withPivot": ["added_at", "added_by"]
}

// Query with pivot data
GET /posts/1?expand=tags

// Response
{
  "id": 1,
  "title": "Getting Started with API Design",
  "content": "...",
  "tags": [
    {
      "id": 5,
      "name": "API Design",
      "pivot": {
        "added_at": "2023-04-15T14:32:10Z",
        "added_by": 42
      }
    },
    // ...
  ]
}

Self-Referential Relationships

Define relationships within the same resource:

// Self-referential: Comments can have replies
"comments": {
  "fields": {
    "id": { "type": "id" },
    "text": { "type": "string" },
    "parentId": { "type": "integer", "reference": "comments.id", "nullable": true },
    "parent": { "type": "relation", "resource": "comments", "relation": "belongsTo" },
    "replies": { "type": "relation", "resource": "comments", "relation": "hasMany", "foreignKey": "parentId" }
  }
}

Polymorphic Relationships

Define relationships that can connect to multiple resource types:

// Polymorphic: Comments can belong to posts or products
"comments": {
  "fields": {
    "id": { "type": "id" },
    "text": { "type": "string" },
    "commentableId": { "type": "integer" },
    "commentableType": { "type": "string" },
    "commentable": {
      "type": "relation",
      "relation": "morphTo",
      "typeField": "commentableType",
      "idField": "commentableId"
    }
  }
},
"posts": {
  "fields": {
    "id": { "type": "id" },
    "title": { "type": "string" },
    "comments": {
      "type": "relation",
      "resource": "comments",
      "relation": "morphMany",
      "typeValue": "posts"
    }
  }
},
"products": {
  "fields": {
    "id": { "type": "id" },
    "name": { "type": "string" },
    "comments": {
      "type": "relation",
      "resource": "comments",
      "relation": "morphMany",
      "typeValue": "products"
    }
  }
}

Best Practices

  • Define relationships on both sides for clarity (e.g., both hasMany and belongsTo)
  • Use descriptive relationship names that reflect their purpose
  • For many-to-many relationships, create a proper junction table with any additional pivot data
  • Consider performance when defining deep relationship hierarchies
  • Use expansion judiciously to avoid oversized response payloads