CRUDResource API Reference
The CRUDResource trait is the core abstraction for CRUD operations.
Trait Definition
The trait declares each async method as fn ... -> impl Future<Output = ...> + Send;
an impl writes async fn as shown here, and the compiler checks that its
future is Send.
pub trait CRUDResource: Sized + Send + Sync {
/// The Sea-ORM entity type
type EntityType: EntityTrait;
/// Model for creating new records
type CreateModel: DeserializeOwned + Send + Sync;
/// Model for updating existing records
type UpdateModel: DeserializeOwned + Send + Sync;
/// Model for list responses
type ListModel: Serialize + Send + Sync;
/// The primary key type
type PrimaryKey: Send + Sync;
/// Get a single record by ID
async fn get_one<C: sea_orm::ConnectionTrait + sea_orm::TransactionTrait>(
db: &C,
id: Self::PrimaryKey,
) -> Result<Self, ApiError>;
/// Get all records with filtering, sorting, and pagination
async fn get_all<C: sea_orm::ConnectionTrait + sea_orm::TransactionTrait>(
db: &C,
condition: Condition,
order: (Self::EntityType::Column, Order),
offset: u64,
limit: u64,
) -> Result<Vec<Self::ListModel>, ApiError>;
/// Create a new record
async fn create<C: sea_orm::ConnectionTrait + sea_orm::TransactionTrait>(
db: &C,
data: Self::CreateModel,
) -> Result<Self, ApiError>;
/// Update an existing record
async fn update<C: sea_orm::ConnectionTrait + sea_orm::TransactionTrait>(
db: &C,
id: Self::PrimaryKey,
data: Self::UpdateModel,
) -> Result<Self, ApiError>;
/// Delete a single record
async fn delete<C: sea_orm::ConnectionTrait + sea_orm::TransactionTrait>(
db: &C,
id: Self::PrimaryKey,
) -> Result<(), ApiError>;
/// Delete multiple records
async fn delete_many<C: sea_orm::ConnectionTrait + sea_orm::TransactionTrait>(
db: &C,
ids: Vec<Self::PrimaryKey>,
) -> Result<u64, ApiError>;
/// Get total count matching condition
async fn total_count<C: sea_orm::ConnectionTrait + sea_orm::TransactionTrait>(
db: &C,
condition: &Condition,
) -> u64;
}
Associated Types
EntityType
The Sea-ORM entity for database operations.
type EntityType = entity::Entity;
CreateModel
Request model for creating records. Excludes auto-generated fields.
type CreateModel = ArticleCreate;
// Generated struct
pub struct ArticleCreate {
pub title: String,
pub content: String,
// id, created_at excluded
}
UpdateModel
Request model for updating records. All fields optional.
type UpdateModel = ArticleUpdate;
// Generated struct
pub struct ArticleUpdate {
pub title: Option<String>,
pub content: Option<String>,
}
ListModel
Response model for list operations. May exclude expensive fields.
type ListModel = ArticleList;
// Generated struct
pub struct ArticleList {
pub id: Uuid,
pub title: String,
// content excluded from list
}
PrimaryKey
Type of the primary key.
type PrimaryKey = Uuid;
// or
type PrimaryKey = i32;
Methods
get_one
Retrieve a single record by primary key.
async fn get_one<C: sea_orm::ConnectionTrait + sea_orm::TransactionTrait>(
db: &C,
id: Self::PrimaryKey,
) -> Result<Self, ApiError>;
Parameters:
db- Database connectionid- Primary key value
Returns:
Ok(Self)- The record as response modelErr(ApiError::NotFound)- Record doesn’t existErr(ApiError::Database)- Database error
Example:
let article = Article::get_one(&db, article_id).await?;
get_all
Retrieve multiple records with filtering, sorting, and pagination.
async fn get_all<C: sea_orm::ConnectionTrait + sea_orm::TransactionTrait>(
db: &C,
condition: Condition,
order: (Self::EntityType::Column, Order),
offset: u64,
limit: u64,
) -> Result<Vec<Self::ListModel>, ApiError>;
Parameters:
db- Database connectioncondition- Sea-ORM condition for filteringorder- Tuple of (Column, Order) for sortingoffset- Number of records to skiplimit- Maximum records to return
Returns:
Ok(Vec<ListModel>)- List of recordsErr(ApiError::Database)- Database error
Example:
use sea_orm::{Condition, Order};
let condition = Condition::all()
.add(Column::Status.eq("published"));
let articles = Article::get_all(
&db,
condition,
(Column::CreatedAt, Order::Desc),
0, // offset
20, // limit
).await?;
create
Create a new record.
async fn create<C: sea_orm::ConnectionTrait + sea_orm::TransactionTrait>(
db: &C,
data: Self::CreateModel,
) -> Result<Self, ApiError>;
Parameters:
db- Database connectiondata- Create model with field values
Returns:
Ok(Self)- Created record as response modelErr(ApiError::Database)- Database error (e.g., constraint violation)
Example:
let new_article = Article::create(&db, ArticleCreate {
title: "Hello World".into(),
content: "My first post".into(),
}).await?;
update
Update an existing record.
async fn update<C: sea_orm::ConnectionTrait + sea_orm::TransactionTrait>(
db: &C,
id: Self::PrimaryKey,
data: Self::UpdateModel,
) -> Result<Self, ApiError>;
Parameters:
db- Database connectionid- Primary key of record to updatedata- Update model (only Some fields are updated)
Returns:
Ok(Self)- Updated record as response modelErr(ApiError::NotFound)- Record doesn’t existErr(ApiError::Database)- Database error
Example:
let updated = Article::update(&db, article_id, ArticleUpdate {
title: Some("New Title".into()),
content: None, // Don't change
}).await?;
delete
Delete a single record.
async fn delete<C: sea_orm::ConnectionTrait + sea_orm::TransactionTrait>(
db: &C,
id: Self::PrimaryKey,
) -> Result<(), ApiError>;
Parameters:
db- Database connectionid- Primary key of record to delete
Returns:
Ok(())- Record deletedErr(ApiError::NotFound)- Record doesn’t existErr(ApiError::Database)- Database error
Example:
Article::delete(&db, article_id).await?;
delete_many
Delete multiple records by IDs.
async fn delete_many<C: sea_orm::ConnectionTrait + sea_orm::TransactionTrait>(
db: &C,
ids: Vec<Self::PrimaryKey>,
) -> Result<u64, ApiError>;
Parameters:
db- Database connectionids- List of primary keys (max 100)
Returns:
Ok(u64)- Number of records deletedErr(ApiError::BadRequest)- More than 100 IDs providedErr(ApiError::Database)- Database error
Example:
let deleted_count = Article::delete_many(&db, vec![id1, id2, id3]).await?;
total_count
Get count of records matching a condition.
async fn total_count<C: sea_orm::ConnectionTrait + sea_orm::TransactionTrait>(
db: &C,
condition: &Condition,
) -> u64;
Parameters:
db- Database connectioncondition- Sea-ORM condition for filtering
Returns:
- Count of matching records (0 on error)
Example:
let condition = Condition::all()
.add(Column::Status.eq("published"));
let count = Article::total_count(&db, &condition).await;
Registration methods
Generated from #[crudcrate(upsert_key(...))] and used by
crudcrate::upsert. A resource without the
attribute keeps the defaults, and upsert refuses rather than guessing a key.
upsert_key
The alternate unique key a source system registers rows under, in the order the index declares them. Empty by default.
fn upsert_key() -> &'static [<Self::EntityType as EntityTrait>::Column];
upsert_predicate
The predicate upsert_key is unique under, for a key backed by a
partial index. Defaults to Condition::all(), which matches every row.
Generated from #[crudcrate(upsert_where = ...)].
fn upsert_predicate() -> Condition;
upsert_comparable
The columns a registration compares and writes: the create model’s stored columns, minus the primary key. Defaults to the key alone.
fn upsert_comparable() -> &'static [<Self::EntityType as EntityTrait>::Column];
apply_on_update
Applies the entity’s on_update expressions to a row a registration is about to
change, so a field the entity maintains itself advances even though no source
sends it. Defaults to doing nothing.
fn apply_on_update(model: &mut Self::ActiveModelType);
Usage in Custom Handlers
use crudcrate::CRUDResource;
async fn custom_list_handler(
Query(params): Query<FilterOptions>,
Extension(db): Extension<DatabaseConnection>,
) -> Result<Json<Vec<ArticleList>>, ApiError> {
let condition = apply_filters::<Entity>(¶ms)?;
let (offset, limit) = parse_pagination(¶ms);
let order = parse_sorting::<Entity>(¶ms);
let articles = Article::get_all(&db, condition, order, offset, limit).await?;
Ok(Json(articles))
}
async fn custom_create_handler(
Extension(db): Extension<DatabaseConnection>,
Json(data): Json<ArticleCreate>,
) -> Result<Json<Article>, ApiError> {
// Additional validation
if data.title.is_empty() {
return Err(ApiError::BadRequest("Title required".into()));
}
let article = Article::create(&db, data).await?;
Ok(Json(article))
}