Keyboard shortcuts

Press ← or β†’ to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Relationships

πŸ“‹ See test

Tasks should belong to users. Let’s connect them.

Add User ID to Tasks

// task.rs
pub struct Model {
    #[sea_orm(primary_key, auto_increment = false)]
    #[crudcrate(primary_key, exclude(create, update), on_create = Uuid::new_v4())]
    pub id: Uuid,

    #[crudcrate(filterable, sortable, fulltext)]
    pub title: String,

    #[crudcrate(fulltext)]
    pub description: Option<String>,

    #[crudcrate(filterable)]
    pub completed: bool,

    #[crudcrate(filterable, sortable)]
    pub priority: i32,

    #[crudcrate(filterable)]  // Filter tasks by user
    pub user_id: Uuid,

    #[crudcrate(sortable, exclude(create, update), on_create = chrono::Utc::now())]
    pub created_at: DateTime<Utc>,

    #[crudcrate(exclude(create, update), on_create = chrono::Utc::now(), on_update = chrono::Utc::now())]
    pub updated_at: DateTime<Utc>,
}

Now you can filter tasks by user:

curl 'http://localhost:3000/tasks?filter={"user_id":"550e8400-e29b-41d4-a716-446655440000"}'

But what if you want to see the user details along with the task?

Add a join field to include the user in task responses:

// task.rs
pub struct Model {
    // ... existing fields ...

    #[crudcrate(filterable)]
    pub user_id: Uuid,

    // Load the user with the task
    #[sea_orm(ignore)]
    #[crudcrate(non_db_attr, join(one, all))]
    pub user: Option<super::user::User>,

    // ... timestamps ...
}

Define the Relation

Sea-ORM needs to know how entities relate:

// task.rs
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
    #[sea_orm(
        belongs_to = "super::user::Entity",
        from = "Column::UserId",
        to = "super::user::Column::Id"
    )]
    User,
}

impl Related<super::user::Entity> for Entity {
    fn to() -> RelationDef {
        Relation::User.def()
    }
}

Response Now Includes User

curl http://localhost:3000/tasks/550e8400-e29b-41d4-a716-446655440000
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "title": "Learn relationships",
  "completed": false,
  "priority": 5,
  "user_id": "660f9511-f30c-42e5-b827-557766551111",
  "user": {
    "id": "660f9511-f30c-42e5-b827-557766551111",
    "email": "[email protected]",
    "name": "Alice",
    "created_at": "2024-01-15T10:00:00Z"
  },
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2024-01-15T10:30:00Z"
}

Join Options

AttributeWhen Data is Loaded
join(one)Only in GET /:id (detail view)
join(all)Only in GET / (list view)
join(one, all)Both endpoints

For performance, use join(one) when the related data is only needed in detail views:

// Only load user in detail view, not in lists
#[crudcrate(non_db_attr, join(one))]
pub user: Option<User>,

The Other Direction

Users can have tasks too. Add to user.rs:

// user.rs
pub struct Model {
    // ... existing fields ...

    // User's tasks (only in detail view)
    #[sea_orm(ignore)]
    #[crudcrate(non_db_attr, join(one))]
    pub tasks: Vec<super::task::Task>,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
    #[sea_orm(has_many = "super::task::Entity")]
    Tasks,
}

impl Related<super::task::Entity> for Entity {
    fn to() -> RelationDef {
        Relation::Tasks.def()
    }
}
curl http://localhost:3000/users/660f9511-f30c-42e5-b827-557766551111
{
  "id": "660f9511-f30c-42e5-b827-557766551111",
  "email": "[email protected]",
  "name": "Alice",
  "tasks": [
    {"id": "...", "title": "Learn relationships", "completed": false},
    {"id": "...", "title": "Build something cool", "completed": false}
  ],
  "created_at": "2024-01-15T10:00:00Z"
}

Depth Limit

πŸ“‹ See test

To prevent infinite recursion (user β†’ tasks β†’ user β†’ tasks β†’ …), use depth:

#[crudcrate(non_db_attr, join(one, depth = 1))]
pub tasks: Vec<Task>,

Default max depth is 5. Self-referencing relations (like categories with subcategories) are automatically limited to depth 1.

πŸ“‹ See test


Try the Recursive Join Example

See relationships in action with a complete example:

cd crudcrate/crudcrate
cargo run --example recursive_join

This demonstrates Customer β†’ Vehicle β†’ Parts relationships with automatic loading.

  • API: http://localhost:3000/customers
  • Docs: http://localhost:3000/docs

Summary

You now have a complete task manager with:

  • Auto-generated UUIDs
  • Timestamps
  • Filtering, sorting, pagination
  • Full-text search
  • Hidden sensitive fields
  • Related data loading

But what if you need custom logic? Like validating input or sending notifications?

Next: Custom logic - add validation and side effects.