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

Blog with Comments Example

A blog API with posts, comments, and authors demonstrating relationships.

Note: This is a reference example. To see relationships in action, run:

cargo run --example recursive_join

Then visit http://localhost:3000/docs


Entities

User (Author)

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize, EntityToModels)]
#[crudcrate(generate_router)]
#[sea_orm(table_name = "users")]
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)]
    pub name: String,

    #[crudcrate(filterable)]
    pub email: String,

    #[crudcrate(exclude(one, list))]
    pub password_hash: String,

    pub bio: Option<String>,

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

    // Relationship: User has many Posts
    #[sea_orm(ignore)]
    #[crudcrate(non_db_attr, join(one))]
    pub posts: Vec<super::post::Post>,
}

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

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

Post

#[derive(Clone, Debug, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
#[sea_orm(rs_type = "String", db_type = "String(StringLen::N(15))")]
pub enum PostStatus {
    #[sea_orm(string_value = "draft")]
    Draft,
    #[sea_orm(string_value = "published")]
    Published,
    #[sea_orm(string_value = "archived")]
    Archived,
}

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize, EntityToModels)]
#[crudcrate(generate_router)]
#[sea_orm(table_name = "posts")]
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,

    pub slug: String,

    #[crudcrate(fulltext, exclude(list))]
    pub content: String,

    pub excerpt: Option<String>,

    #[crudcrate(filterable)]
    pub status: PostStatus,

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

    #[crudcrate(sortable, filterable)]
    pub published_at: Option<DateTimeUtc>,

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

    // Author (belongs_to)
    #[sea_orm(ignore)]
    #[crudcrate(non_db_attr, join(one, all, depth = 1))]
    pub author: Option<super::user::User>,

    // Comments (has_many) - only in detail view
    #[sea_orm(ignore)]
    #[crudcrate(non_db_attr, join(one))]
    pub comments: Vec<super::comment::Comment>,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
    #[sea_orm(
        belongs_to = "super::user::Entity",
        from = "Column::AuthorId",
        to = "super::user::Column::Id"
    )]
    Author,

    #[sea_orm(has_many = "super::comment::Entity")]
    Comments,
}

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

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

Comment

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize, EntityToModels)]
#[crudcrate(generate_router)]
#[sea_orm(table_name = "comments")]
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(fulltext)]
    pub content: String,

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

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

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

    // Comment author
    #[sea_orm(ignore)]
    #[crudcrate(non_db_attr, join(one, all, depth = 1))]
    pub author: Option<super::user::User>,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
    #[sea_orm(
        belongs_to = "super::post::Entity",
        from = "Column::PostId",
        to = "super::post::Column::Id"
    )]
    Post,

    #[sea_orm(
        belongs_to = "super::user::Entity",
        from = "Column::AuthorId",
        to = "super::user::Column::Id"
    )]
    Author,
}

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

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

Router Setup

let app = Router::new()
    .merge(user::user_router())
    .merge(post::post_router())
    .merge(comment::comment_router())
    .layer(Extension(db));

API Examples

Create a User

curl -X POST http://localhost:3000/users \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Alice",
    "email": "[email protected]",
    "password_hash": "hashed_password",
    "bio": "Software developer"
  }'

Create a Post

curl -X POST http://localhost:3000/posts \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Getting Started with Rust",
    "slug": "getting-started-with-rust",
    "content": "Full article content...",
    "excerpt": "Learn the basics of Rust programming",
    "status": "published",
    "author_id": "{user-id}",
    "published_at": "2024-01-15T10:00:00Z"
  }'

List Published Posts with Authors

curl "http://localhost:3000/posts?filter={\"status\":\"published\"}&sort=[\"published_at\",\"DESC\"]"

Get Post with Comments

curl "http://localhost:3000/posts/{post-id}"

# Response includes author and comments
{
  "id": "...",
  "title": "Getting Started with Rust",
  "author": {
    "id": "...",
    "name": "Alice"
  },
  "comments": [
    {
      "id": "...",
      "content": "Great article!",
      "author": {"name": "Bob"}
    }
  ]
}

Add a Comment

curl -X POST http://localhost:3000/comments \
  -H "Content-Type: application/json" \
  -d '{
    "content": "This really helped me understand Rust!",
    "post_id": "{post-id}",
    "author_id": "{user-id}"
  }'

Search Posts

curl "http://localhost:3000/posts?q=rust programming"

Next: See the E-commerce Example for complex entity relationships with custom operations.