{"id":37231706,"url":"https://github.com/evanjt/crudcrate","last_synced_at":"2026-01-15T03:45:29.373Z","repository":{"id":332284319,"uuid":"934717801","full_name":"evanjt/crudcrate","owner":"evanjt","description":"Rust traits and functions to aid in building CRUD APIs in Axum and Sea-ORM","archived":false,"fork":false,"pushed_at":"2025-11-27T06:48:45.000Z","size":2058,"stargazers_count":6,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-13T09:46:29.456Z","etag":null,"topics":["api","axum","crud","rest","sea-orm"],"latest_commit_sha":null,"homepage":"https://crates.io/crates/crudcrate","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/evanjt.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-02-18T09:45:02.000Z","updated_at":"2026-01-08T01:34:46.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/evanjt/crudcrate","commit_stats":null,"previous_names":["evanjt/crudcrate"],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/evanjt/crudcrate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evanjt%2Fcrudcrate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evanjt%2Fcrudcrate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evanjt%2Fcrudcrate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evanjt%2Fcrudcrate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/evanjt","download_url":"https://codeload.github.com/evanjt/crudcrate/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/evanjt%2Fcrudcrate/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28416197,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T08:38:59.149Z","status":"ssl_error","status_checked_at":"2026-01-14T08:38:43.588Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","axum","crud","rest","sea-orm"],"created_at":"2026-01-15T03:45:28.831Z","updated_at":"2026-01-15T03:45:29.359Z","avatar_url":"https://github.com/evanjt.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# crudcrate\n\n[![Tests](https://github.com/evanjt/crudcrate/actions/workflows/test.yml/badge.svg)](https://github.com/evanjt/crudcrate/actions/workflows/test.yml)\n[![codecov](https://codecov.io/gh/evanjt/crudcrate/branch/main/graph/badge.svg)](https://codecov.io/gh/evanjt/crudcrate)\n[![Crates.io](https://img.shields.io/crates/v/crudcrate.svg)](https://crates.io/crates/crudcrate)\n[![Documentation](https://docs.rs/crudcrate/badge.svg)](https://docs.rs/crudcrate)\n\nTired of writing boilerplate for your APIs? Frustrated that your API models look almost identical to your database models, but you have to maintain both? What if you could get a complete CRUD API running in minutes, then customize only the parts that need special handling?\n\n**crudcrate** transforms your Sea-ORM entities into fully-featured REST APIs with one line of code.\n\n```rust\nuse crudcrate::EntityToModels;\n\n#[derive(EntityToModels)]\n#[crudcrate(generate_router)]\npub struct Model {\n    #[crudcrate(primary_key, exclude(create, update))]\n    pub id: Uuid,\n    #[crudcrate(filterable, sortable)]\n    pub title: String,\n    #[crudcrate(filterable)]\n    pub completed: bool,\n}\n\n// That's it. You now have:\n// - Complete CRUD endpoints (GET, POST, PUT, DELETE)\n// - Auto-generated API models (Todo, TodoCreate, TodoUpdate, TodoList)\n// - Filtering, sorting, and pagination\n// - OpenAPI documentation\n```\n\n## The Problem We're Solving\n\nYou've been here before:\n\n1. **Write your database model** - `Customer` with id, name, email, created_at\n2. **Create API response model** - Basically the same as Customer, but with serde attributes\n3. **Create request model for POST** - Same as Customer, but without id and created_at\n4. **Create update model for PUT** - Same as POST, but all fields optional\n5. **Write 6 HTTP handlers** - get_all, get_one, create, update, delete, delete_many\n6. **Wire up routes** - Map each handler to an endpoint\n7. **Add filtering logic** - Parse query params, build database conditions\n8. **Add pagination** - Calculate offsets, limit results\n9. **Add sorting** - Parse sort parameters, apply to queries\n10. **Add validation** - Make sure fields are correct types\n11. **Add error handling** - Return proper HTTP status codes\n12. **Add OpenAPI docs** - Document all endpoints manually\n\nAnd you repeat this for every single entity in your application.\n\n## Our Solution\n\nLet crudcrate handle the repetitive stuff:\n\n```rust\n#[derive(EntityToModels)]\n#[crudcrate(generate_router)]\npub struct Customer {\n    #[crudcrate(primary_key, exclude(create, update), on_create = Uuid::new_v4())]\n    pub id: Uuid,\n    #[crudcrate(filterable, sortable)]\n    pub name: String,\n    #[crudcrate(filterable)]\n    pub email: String,\n    #[crudcrete(exclude(create, update), on_create = Utc::now())]\n    pub created_at: DateTime\u003cUtc\u003e,\n}\n\n// Just plug it in:\nlet app = Router::new()\n    .nest(\"/api/customers\", Customer::router(\u0026db));\n```\n\n**What you get instantly:**\n\n- `GET /api/customers` - List with filtering, sorting, pagination\n- `GET /api/customers/{id}` - Get single customer\n- `POST /api/customers` - Create new customer\n- `PUT /api/customers/{id}` - Update customer\n- `DELETE /api/customers/{id}` - Delete customer\n- Auto-generated `Customer`, `CustomerCreate`, `CustomerUpdate`, `CustomerList` models\n- Built-in filtering: `?filter={\"name_like\":\"John\"}`\n- Built-in sorting: `?sort=name\u0026order=DESC` or `?sort=[\"name\",\"DESC\"]`\n- Built-in pagination: `?page=1\u0026per_page=20` or `?range=[0,19]` (React Admin)\n\n## But What If I Need Custom Logic?\n\nThat's where crudcrate shines. You get the basics for free, but can override anything:\n\n```rust\n// Need custom validation or permissions?\n#[crudcrate(fn_get_one = custom_get_one)]\npub struct Customer { /* ... */ }\n\nasync fn custom_get_one(db: \u0026DatabaseConnection, id: Uuid) -\u003e Result\u003cCustomer, DbErr\u003e {\n    // Add your custom logic here\n    let customer = Entity::find_by_id(id)\n        .filter(Column::UserId.eq(current_user_id()))  // Permission check\n        .one(db)\n        .await?\n        .ok_or(DbErr::RecordNotFound(\"Customer not found\"))?;\n\n    // Add logging, caching, audit trails, etc.\n    log::info!(\"Customer {} accessed by user {}\", id, current_user_id());\n\n    Ok(customer.into())\n}\n```\n\nOverride any operation: `fn_get_one`, `fn_get_all`, `fn_create`, `fn_update`, `fn_delete`, `fn_delete_many`\n\n## Generated Models\n\nOne entity becomes four specialized models:\n\n```rust\n#[derive(EntityToModels)]\npub struct Model {\n    pub id: Uuid,\n    pub title: String,\n    pub completed: bool,\n    pub secret_data: String,  // Sensitive field\n}\n\n// Generated models:\n\npub struct Todo {           // API responses (get_one)\n    pub id: Uuid,\n    pub title: String,\n    pub completed: bool,\n    // secret_data excluded - sensitive info never sent to clients\n}\n\npub struct TodoCreate {     // POST requests (excluded fields omitted)\n    pub title: String,\n    pub completed: bool,\n    // id and secret_data excluded automatically\n}\n\npub struct TodoUpdate {     // PUT requests (all fields optional)\n    pub title: Option\u003cString\u003e,\n    pub completed: Option\u003cbool\u003e,\n    // id excluded, secret_data excluded unless you override\n}\n\npub struct TodoList {       // List responses (can exclude expensive fields)\n    pub id: Uuid,\n    pub title: String,\n    pub completed: bool,\n    // secret_data excluded to avoid leaking sensitive info in lists\n}\n```\n\n## Real-World Features You'll Actually Use\n\n### Smart Filtering\n\n```rust\n#[crudcrate(filterable, sortable, fulltext)]\npub title: String,\n#[crudcrate(filterable)]\npub priority: i32,\n```\n\nYour users can now:\n\n```bash\n# Exact matches\nGET /api/tasks?filter={\"completed\":false,\"priority\":3}\n\n# Numeric ranges\nGET /api/tasks?filter={\"priority_gte\":2,\"priority_lte\":5}\n\n# Text search across all searchable fields\nGET /api/tasks?filter={\"q\":\"urgent review\"}\n\n# Combine filters\nGET /api/tasks?filter={\"completed\":false,\"priority_gte\":3,\"q\":\"urgent\"}\n```\n\n### Relationship Loading\n\nAutomatically load related data in API responses with full recursive support:\n\n```rust\npub struct Customer {\n    pub id: Uuid,\n    pub name: String,\n\n    // Automatically load related vehicles in API responses\n    #[sea_orm(ignore)]\n    #[crudcrate(non_db_attr, join(one, all))]\n    pub vehicles: Vec\u003cVehicle\u003e,\n}\n\npub struct Vehicle {\n    pub id: Uuid,\n    pub make: String,\n\n    // Each vehicle automatically loads its parts and maintenance records\n    #[sea_orm(ignore)]\n    #[crudcrate(non_db_attr, join(one, all))]\n    pub parts: Vec\u003cVehiclePart\u003e,\n\n    #[sea_orm(ignore)]\n    #[crudcrate(non_db_attr, join(one, all))]\n    pub maintenance_records: Vec\u003cMaintenanceRecord\u003e,\n}\n```\n\n**Multi-level recursive loading works out of the box:**\n- Customer → Vehicles → Parts/Maintenance Records (3 levels deep)\n- No complex SQL joins required - uses efficient recursive queries\n- Automatic cycle detection prevents infinite recursion\n\n**Join options:**\n- `join(one)` - Load only in individual item responses\n- `join(all)` - Load only in list responses\n- `join(one, all)` - Load in both types of responses\n- `join(one, all, depth = 2)` - Custom depth guidance (default: unlimited)\n\n### Field Control\n\nSometimes certain fields shouldn't be in certain models:\n\n```rust\n// Password hash: never send to clients, never allow updates\n#[crudcrate(exclude(one, create, update, list))]\npub password_hash: String,\n\n// API keys: generate server-side, never expose in any response\n#[crudcrate(exclude(one, create, update, list), on_create = generate_api_key())]\npub api_key: String,\n\n// Internal notes: exclude from list (expensive) but show in detail view\n#[crudcrate(exclude(list))]\npub internal_notes: String,\n\n// Timestamps: manage automatically\n#[crudcrate(exclude(create, update), on_create = Utc::now(), on_update = Utc::now())]\npub updated_at: DateTime\u003cUtc\u003e,\n```\n\n**Exclusion options:**\n- `exclude(one)` - Exclude from get_one responses (main API response)\n- `exclude(create)` - Exclude from POST request models\n- `exclude(update)` - Exclude from PUT request models\n- `exclude(list)` - Exclude from list responses\n- `exclude(one, list)` - Exclude from both individual and list responses\n- `exclude(create, update)` - Exclude from both request models\n\n## Production Ready\n\ncrudcrate isn't just a toy - it's built for real applications:\n\n### Database Optimizations\n\n```rust\n// Get performance recommendations for production\ncrudcrate::analyse_all_registered_models(\u0026db, false).await;\n```\n\nOutput:\n\n```\nHIGH Priority:\n  customers - Fulltext search on name/email without proper index\n    CREATE INDEX idx_customers_fulltext ON customers USING GIN (to_tsvector('english', name || ' ' || email));\n\nMEDIUM Priority:\n  customers - Field 'email' is filterable but not indexed\n    CREATE INDEX idx_customers_email ON customers (email);\n```\n\n### Multi-Database Support\n\n- **PostgreSQL**: Full GIN index support, tsvector optimization\n- **MySQL**: FULLTEXT indexes, MATCH AGAINST queries\n- **SQLite**: LIKE-based fallback (perfect for development)\n\n### Battle-Tested Features\n\n- SQL injection prevention via Sea-ORM parameterization\n- Input validation and sanitization\n- Type-safe compile-time checks\n- Comprehensive test suite across all supported databases\n\n## Security\n\ncrudcrate includes several built-in security limits to protect your application from common attack vectors.\n\n### Batch Operation Limits\n\n**Default: 100 items per batch delete**\n\nThe default `delete_many` implementation limits batch deletions to 100 items to prevent DoS attacks via resource exhaustion.\n\n**To increase this limit**, provide a custom implementation:\n\n```rust\n#[crudcrate(fn_delete_many = custom_delete_many)]\nasync fn custom_delete_many(\n    db: \u0026DatabaseConnection,\n    ids: Vec\u003cUuid\u003e\n) -\u003e Result\u003cVec\u003cUuid\u003e, DbErr\u003e {\n    const MAX_SIZE: usize = 500; // Your custom limit\n    if ids.len() \u003e MAX_SIZE {\n        return Err(DbErr::Custom(format!(\"Too many items\")));\n    }\n    // Your implementation...\n}\n```\n\n### Join Depth Limits\n\n**Default: Maximum depth of 5**\n\nRecursive joins are automatically capped at depth 5 to prevent:\n- Infinite recursion with circular references\n- Exponential database query growth (N+1 problem)\n- Database connection pool exhaustion\n\n```rust\n// Shallow joins - load one level only\n#[crudcrate(join(all, depth = 1))]\npub users: Vec\u003cUser\u003e\n\n// Medium depth - 3 levels\n#[crudcrate(join(all, depth = 3))]\npub organization: Option\u003cOrganization\u003e\n\n// Maximum depth - defaults to 5 if unspecified\n#[crudcrate(join(all))]  // depth = 5\npub vehicles: Vec\u003cVehicle\u003e\n\n// Values \u003e 5 are automatically capped to 5\n#[crudcrate(join(all, depth = 10))]  // Will be capped to 5\n```\n\n**Compile-time warnings**: If you specify `depth \u003e 5`, you'll get a compile-time error informing you of the cap.\n\nSee [SECURITY_AUDIT.md](SECURITY_AUDIT.md) for complete security details.\n\n## Quick Start\n\n```bash\ncargo add crudcrate sea-orm axum\n```\n\n```rust\nuse axum::Router;\nuse crudcrate::EntityToModels;\nuse sea_orm::entity::prelude::*;\n\n#[derive(EntityToModels)]\n#[crudcrate(generate_router)]\npub struct Task {\n    #[crudcrate(primary_key, exclude(create, update), on_create = Uuid::new_v4())]\n    pub id: Uuid,\n    #[crudcrate(sortable, filterable)]\n    pub title: String,\n    #[crudcrate(filterable)]\n    pub completed: bool,\n}\n\n#[tokio::main]\nasync fn main() {\n    let db = sea_orm::Database::connect(\"sqlite::memory:\").await.unwrap();\n\n    let app = Router::new()\n        .nest(\"/api/tasks\", Task::router(\u0026db));\n\n    let listener = tokio::net::TcpListener::bind(\"0.0.0.0:3000\").await.unwrap();\n    println!(\"🚀 API running on http://localhost:3000\");\n    axum::serve(listener, app).await.unwrap();\n}\n```\n\nThat's it. You have a complete, production-ready CRUD API.\n\nRun it:\n\n```bash\ncargo run\n```\n\nTest it:\n\n```bash\n# Create a task\ncurl -X POST http://localhost:3000/api/tasks \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"title\":\"Build CRUD API\",\"completed\":false}'\n\n# List all tasks\ncurl http://localhost:3000/api/tasks\n\n# Get a specific task\ncurl http://localhost:3000/api/tasks/{id}\n\n# Update a task\ncurl -X PUT http://localhost:3000/api/tasks/{id} \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"completed\":true}'\n```\n\n## When to Use crudcrate\n\n**Perfect for:**\n\n- Quick prototypes and MVPs\n- Admin panels and internal tools\n- Standard CRUD operations\n- APIs that follow REST conventions\n- Teams that want to move fast\n\n**Maybe not for:**\n\n- Highly specialized endpoints\n- GraphQL APIs (though you could use the generated models)\n- Complex business logic that doesn't fit CRUD patterns\n- When you need full control over every detail\n\n## Examples\n\n```bash\n# Minimal todo API\ncargo run --example minimal\n\n# Relationship loading demo\ncargo run --example recursive_join\n```\n\n## License\n\nMIT License. See [LICENSE](./LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fevanjt%2Fcrudcrate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fevanjt%2Fcrudcrate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fevanjt%2Fcrudcrate/lists"}