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

E-commerce Orders Example

Order management with products, customers, and line items demonstrating deep joins.

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

cargo run --example recursive_join

This shows Customer → Vehicle → Parts/Maintenance (similar to Order → Items → Products).


Entities

Product

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

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

    #[crudcrate(filterable, sortable)]
    pub price: Decimal,

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

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

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

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

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}

impl ActiveModelBehavior for ActiveModel {}

Customer

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

    pub phone: Option<String>,

    pub shipping_address: Option<String>,

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

    // Customer's orders
    #[sea_orm(ignore)]
    #[crudcrate(non_db_attr, join(one))]
    pub orders: Vec<super::order::Order>,
}

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

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

Order

#[derive(Clone, Debug, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
#[sea_orm(rs_type = "String", db_type = "String(StringLen::N(20))")]
pub enum OrderStatus {
    #[sea_orm(string_value = "pending")]
    Pending,
    #[sea_orm(string_value = "confirmed")]
    Confirmed,
    #[sea_orm(string_value = "shipped")]
    Shipped,
    #[sea_orm(string_value = "delivered")]
    Delivered,
    #[sea_orm(string_value = "cancelled")]
    Cancelled,
}

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize, EntityToModels)]
#[crudcrate(generate_router, operations = OrderOperations)]
#[sea_orm(table_name = "orders")]
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)]
    pub customer_id: Uuid,

    #[crudcrate(filterable, sortable)]
    pub status: OrderStatus,

    #[crudcrate(sortable)]
    pub total_amount: Decimal,

    pub shipping_address: String,

    pub notes: Option<String>,

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

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

    // Customer
    #[sea_orm(ignore)]
    #[crudcrate(non_db_attr, join(one, all, depth = 1))]
    pub customer: Option<super::customer::Customer>,

    // Line items
    #[sea_orm(ignore)]
    #[crudcrate(non_db_attr, join(one))]
    pub items: Vec<super::order_item::OrderItem>,
}

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

    #[sea_orm(has_many = "super::order_item::Entity")]
    Items,
}

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

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

Order Item

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

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

    pub quantity: i32,

    pub unit_price: Decimal,

    pub total_price: Decimal,

    // Product details at time of order
    #[sea_orm(ignore)]
    #[crudcrate(non_db_attr, join(one, all, depth = 1))]
    pub product: Option<super::product::Product>,
}

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

    #[sea_orm(
        belongs_to = "super::product::Entity",
        from = "Column::ProductId",
        to = "super::product::Column::Id"
    )]
    Product,
}

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

Custom Operations

pub struct OrderOperations;

#[async_trait]
impl CRUDOperations for OrderOperations {
    type Resource = Order;

    async fn before_create(
        &self,
        db: &DatabaseConnection,
        data: &mut OrderCreate,
    ) -> Result<(), ApiError> {
        // Calculate total from items
        // Validate stock availability
        Ok(())
    }

    async fn after_create(
        &self,
        db: &DatabaseConnection,
        created: &Order,
    ) -> Result<(), ApiError> {
        // Update stock quantities
        // Send confirmation email
        Ok(())
    }

    async fn before_update(
        &self,
        db: &DatabaseConnection,
        id: Uuid,
        data: &mut OrderUpdate,
    ) -> Result<(), ApiError> {
        let order = Entity::find_by_id(id).one(db).await?.ok_or(ApiError::NotFound)?;

        // Prevent changing cancelled orders
        if order.status == OrderStatus::Cancelled {
            return Err(ApiError::BadRequest("Cannot modify cancelled order".into()));
        }

        // Validate status transitions
        if let Some(new_status) = &data.status {
            if !is_valid_transition(&order.status, new_status) {
                return Err(ApiError::BadRequest("Invalid status transition".into()));
            }
        }

        Ok(())
    }
}

API Examples

Create Order

curl -X POST http://localhost:3000/orders \
  -H "Content-Type: application/json" \
  -d '{
    "customer_id": "{customer-id}",
    "status": "pending",
    "total_amount": "99.99",
    "shipping_address": "123 Main St, City, Country"
  }'

Get Order with Items

curl "http://localhost:3000/orders/{order-id}"

# Response
{
  "id": "...",
  "status": "confirmed",
  "customer": {"name": "John Doe", "email": "..."},
  "items": [
    {"product": {"name": "Widget"}, "quantity": 2, "total_price": "49.98"}
  ]
}

Filter Orders by Status

# Pending orders
curl "http://localhost:3000/orders?filter={\"status\":\"pending\"}"

# Customer's orders
curl "http://localhost:3000/orders?filter={\"customer_id\":\"{id}\"}"

Update Order Status

curl -X PUT http://localhost:3000/orders/{id} \
  -H "Content-Type: application/json" \
  -d '{"status": "shipped"}'

Ready to dive deeper? Check out the Tutorial for step-by-step learning, or the Reference for all available options.