| name | build-rust-apps |
| description | Build or modify Rust backend applications using the user's preferred structure and defaults. Use when working on Rust APIs, backend services, app scaffolding, routes, services, dependency integrations, models, Toasty CLI migrations, OpenAPI, validation, observability, event logging, tests, or Makefile-driven Rust workflows. |
Build Rust Apps
Core Defaults
Build Rust backends as service-oriented applications. Prefer Rust 2024, tokio, Axum-style HTTP APIs, utoipa for OpenAPI, validator/typed extractors for request validation, tracing for logs, metrics, and Sentry/OpenTelemetry hooks where the app already uses them.
Use UUIDv7 for persistent IDs unless a surrounding system already chose another identifier strategy.
Use asymmetric signing algorithms for JWTs. Prefer private-key signing and public-key verification over shared symmetric secrets.
Prefer Makefile targets for common actions. When adding commands, include formatting, linting, testing, building, Docker Compose validation, and service-specific targets.
Structure
Use this default source layout:
src/
├── main.rs
├── lib.rs
├── routes/
├── services/
├── libs/
├── middleware/
├── models/
├── state.rs
├── errors.rs
└── openapi.rs
Keep boundaries strict:
routes: API endpoint registration and handlers only. Parse request input, require auth/permissions, call services, and shape HTTP responses.
services: internal business logic and use-case orchestration. Put transactions, domain decisions, workflows, and cross-model coordination here.
libs: integrations with dependencies and infrastructure, such as database setup, Redis, Resend, Temporal, Sentry, telemetry, config, Docker, email, queue clients, and external APIs.
models: application and domain models. Use this name instead of entities.
middleware: HTTP middleware such as auth, allowed hosts, request context, CORS, and metrics.
state.rs: shared app state passed into handlers.
errors.rs: app error type, API error conversion, and unexpected-error capture helpers.
Do not put business rules in route handlers. Do not put dependency client setup in services unless it is service-specific and not reusable.
Do not expose raw models directly to API callers or external service callers. Let service logic work with models internally, then map outputs into explicit response DTOs or public types at the boundary.
API Pattern
Routes should be thin and typed:
- Define request/response DTOs near the route when they are endpoint-specific.
- Use validation derives or extractors before calling a service.
- Use path/query/body extractors explicitly.
- Return a consistent app error type.
- Attach
utoipa::path metadata for public endpoints.
- Keep OpenAPI response bodies aligned with actual response types.
Prefer service constructors like ProjectService::new(state.db.clone()) or borrowed equivalents that match the app's state model.
Do not put standalone helper functions in route files. Move helpers into services, libs, DTO modules, traits, impl blocks, or dedicated files. Keep route files focused on endpoint registration and handlers.
Data And Migrations
Use Toasty for models and migrations in new Rust apps. Use the Toasty CLI migration setup, not schema push. Follow the CLI workflow from Toasty's schema management guide and keep migrations checked in. Do not create new SeaORM migrations unless the existing project is already committed to SeaORM and the task is a local change inside that project.
For data work:
- Keep model definitions in
src/models.
- Keep migration definitions in the Toasty migration structure used by the app.
- Wrap multi-write use cases in transactions inside services.
- Map low-level database errors into the app error type at the boundary.
- Use UUIDv7 IDs at creation time.
Observability And Operations
Initialize config, telemetry, database connections, and bootstrap tasks in startup code before binding the server. Add graceful shutdown for long-running services and workers.
For runtime behavior:
- Create a wide request event log context in middleware. Pass the context through services/classes/functions so each layer can attach standardized request, auth, domain, dependency, timing, and outcome details. Log the completed event once at the end of the request lifecycle.
- Use
tracing::instrument on meaningful service operations and handlers, skipping secrets and large payloads.
- Capture unexpected errors with the app's Sentry/error helper.
- Expose metrics middleware when the app has Prometheus support.
- Keep secrets out of logs.
- Put Temporal workers or background runners in
src/bin.
Testing And Verification
For changes, prefer:
- Unit tests for pure service helpers.
- Integration tests for API behavior, auth, persistence, and workflow boundaries. Run persistence tests against the Docker Compose database or inside Docker context by default unless the tests truly do not need Docker.
- Migration/model tests when changing persistence.
cargo fmt --all -- --check, cargo clippy --workspace --all-targets --locked -- -D warnings, and cargo test --workspace --locked when applicable.
If the repo has a Makefile, use the closest make fmt-check, make lint, make test, or service-specific target instead of inventing commands.