Rust service
Services where runtime control and explicit resource handling matter.
Axum · Tokio · SQLx · PostgreSQL
Complete guide (Markdown) · Structured record (JSON)
Original source review: 2026-10-10. Core guidance rechecked: 2026-10-13. Check scope. Dependency combinations and setup need project-specific qualification.
Use cases
- Native network services
- resource-sensitive APIs
- services sharing a Rust core
Choose it when
- Memory/resource control or Rust library reuse is important
- Team accepts ownership/async complexity for compile-time guarantees
Consider another path when
- Primary goal is conventional CRUD delivery with minimal framework assembly
- Team cannot maintain Rust async code
- A full product framework is needed immediately
Working practices
- Keep blocking work off async executor threads and apply timeouts and concurrency limits.
- Use typed configuration and tracing; preserve useful context when returning errors.
- Rehearse SQLx migrations and validate prepared query metadata against the intended schema.
- Build for the actual deployment target. Type checks do not replace authorization, retry and load tests.
Tools by job
HTTP server: Axum + Tokio + tower-http
Typed extractors/state and Tower middleware compose with Tokio networking.
Configure limits, timeout/error mapping and graceful shutdown. Blocking work must not monopolize async workers.
Database and migrations: PostgreSQL + SQLx; sqlx-cli migrations
SQL-oriented async access with optional checked query macros and migration tooling.
Select runtime/TLS/database features for the resolved release. Checked macros need a database or maintained offline metadata; runtime queries are not compile-time checked.
Configuration: Serde-backed settings + config
Typed settings plus structured events/spans.
Set precedence explicitly; validate semantics; install a tracing subscriber; never log secrets.
Application logging: tracing + configured subscriber
Typed settings plus structured events/spans.
Set precedence explicitly; validate semantics; install a tracing subscriber; never log secrets.
Tests and static checks: cargo test; rustfmt; Clippy; cargo audit
Compiler, style, behavior and known-advisory checks complement each other.
Audit coverage is advisory-driven; compile success does not establish behavior or security. Test supported feature combinations rather than blindly enabling incompatible features.
Durable work: None for request-only service; Restate Rust SDK when durable workflows are required
Durable handlers and persisted workflow progress address work outliving a process.
Requires a Restate service and adoption test. Journal external operations correctly; idempotency and business transactions remain explicit. Tokio tasks alone are not durable.
Deployment: cargo build --release --locked in a target-specific build pipeline
Reproducible dependency resolution from committed lockfile; native executable artifact.
Not universally static: verify libc, TLS/native libraries, target support, certificates and container runtime needs.
API token verification: jsonwebtoken + selected identity provider contract
Typed claims and signature checks fit JWT-bearing service APIs
Configure accepted algorithms, issuer/audience, expiry and trusted key rotation; select required crypto features for resolved release. This is a verifier, not an OIDC login/session solution; opaque tokens need provider introspection.
Transactional email: lettre + project-selected SMTP provider
Typed mail construction and SMTP transport support application messages
Configure TLS, sender credentials and timeouts; send required mail through durable jobs with duplicate protection. Validate chosen async/runtime features in Rust; delivery receipts and bounces need provider handling.
Uploads and object storage: AWS SDK for Rust S3 client
Durable object storage avoids dependence on service-local disk
Configure runtime credentials, region and least-privilege bucket access; authorize object keys and uploads, bound size/concurrency and define retention. Test multipart cleanup and selected provider compatibility.
Realtime transport: Axum WebSocket extractor
WebSocket support fits bidirectional connected clients
Enable the documented module/feature; authorize handshake and every protected message, bound queues/message size and implement heartbeat/shutdown. Socket delivery is not durable history; define reconnect/resync.
Build with the stack’s strengths
Architecture and developer workflow.
Recommendation: Axum/Tower owns request extraction and middleware; domain functions return explicit errors; Tokio drives async I/O; SQLx pools and transactions own database access. Track child tasks and cancellation separately from persisted jobs. A Tokio channel coordinates tasks inside one live process and cannot recover accepted work after a crash.
Version scope: Reviewed docs expose Axum 0.8.9, Tokio 1.53.2 and SQLx 0.9.0 on 11 October 2026; Rust std docs show 1.99.0. These are documentation scopes, not a qualified composition or an automatic upgrade recommendation. Record rust-toolchain.toml, Cargo.lock, enabled features, SQLx CLI compatibility and database version before implementation. · Reviewed 2026-10-11
Architecture boundaries
- Recommendation: route through Axum extractors and typed State with cloneable pool/client handles. Keep domain logic independent of HTTP responses, and map business/infrastructure errors deliberately through IntoResponse.
- Recommendation: compose Tower limits and timeouts with explicit overload/error mapping. Use bounded channels/semaphores; a concurrency limit without an admission/queue policy does not bound every waiting request.
- Recommendation: use SQLx query macros where schema checking fits and dynamic queries where needed. Store reviewed migrations and offline metadata; runtime authorization and isolation remain separate from compile-time type checks.
- Recommendation: move sustained CPU work to a bounded blocking executor or dedicated worker process. Track async tasks with cancellation/termination coordination; a started spawn_blocking task cannot be forcibly aborted through Tokio.
Tools and feedback
- Recommendation: inspect Cargo.toml/Cargo.lock, rust-toolchain.toml and enabled dependency features. For a new project, cargo new service and cargo add axum can establish the package; select Tokio runtime and SQLx database/TLS features deliberately rather than copying a broad feature list.
- Recommendation: run cargo fmt --check, cargo clippy --all-targets -- -D warnings and cargo test --locked with the project-supported feature matrix. Compile and run protocol/cancellation fixtures; no integration was executed in this research.
- Recommendation: install a SQLx CLI version compatible with the selected crate; against a disposable database run cargo sqlx migrate run, then cargo sqlx prepare --workspace and commit .sqlx metadata. CI cargo sqlx prepare --check --workspace detects stale metadata; an offline build does not prove the live schema matches.
- Recommendation: cargo build --release --locked prepares a release for the selected target. Use tracing/request IDs, pool-wait metrics and representative load profiles before asserting latency, cost or capacity advantages.
Translations to avoid
- Avoid Arc<Mutex<Connection>> for all queries; a shared pool permits independent requests without a global database serialization boundary.
- Avoid blocking SDK calls or heavy loops on async runtime workers. spawn_blocking needs its own capacity limits and cannot abort a running closure.
- Avoid using Tokio mpsc as a drop-in replacement for durable Redis jobs; accepted jobs disappear with memory unless independently persisted.
- Avoid confusing Rust memory safety with correct authorization, queue delivery or compatibility guarantees.
For an existing application
Keep the contracts while you change the stack.
These are documented options and proposed checks. Inspect the actual source application before adopting a transition.
Data and schema
Inspect the source
Capture the old SQL schema, transactions, tenant filters, nullable values and database engine behavior.
During coexistence
Recommendation: keep existing schema ownership and backward-compatible columns; run SQLx reads for a selected boundary before transferring its writer. Generate metadata against exactly the reviewed migration state.
Target design
Recommendation: SQLx pooled access with transaction-scoped domain changes; query! checks literals against a live build schema or checked-in .sqlx metadata. Keep database engine choice independent of the Rust rewrite.
Rollback
Recommendation: route writes back while retaining additive schema and a previous compatible binary. Delay destructive migrations; reconcile new-only persisted values before rollback.
Benefits and prerequisites
Compile-time query/type checks can expose SQL/API mismatches before deployment.
Requires: Query macros/features and metadata match the chosen engine and schema; nullable results and dynamic queries receive runtime checks.
Tradeoffs: Dynamic SQL is outside macro checking; stale offline metadata can compile; schema ownership and deployment order still need controls.
Checks before transfer
- Run an offline locked build with the reviewed metadata and a live prepare --check against the migrated fixture DB.
- Verify both runtimes read each other's writes, including null/time/ID mappings.
- Cancel during a transaction and confirm the intended committed/rolled-back state plus pool recovery.
Jobs and schedules
Inspect the source
Preserve old broker serialization, acknowledgement, delayed/retry states, concurrency and effect IDs. Durable Redis workers cannot be replaced transparently by tokio::spawn.
During coexistence
Recommendation: Rust commits an outbox intent in its domain transaction; a replay-safe relay submits to the retained queue/worker interface. Existing workers retain queue ownership while new job kinds are qualified. This outbox design is inferred, not a shipped Axum feature.
Target design
Recommendation: keep proven durable workers until a chosen Rust queue implementation is qualified for retries, scheduling, leases and crashes. River current Ruby docs describe a shared schema with Rust; that statement alone does not qualify a Rust worker API or migration.
Rollback
Recommendation: stop the new producer, let accepted outbox entries settle and retain the old worker. Roll back traffic only after ambiguous effects are reconciled; avoid consuming a queue with incompatible handlers.
Benefits and prerequisites
A retained worker boundary allows the HTTP layer to change without dropping backlog or broker guarantees.
Requires: Same-transaction outbox intent; stable relay/effect IDs; explicit payload translator; observed delivery and backlog ownership.
Tradeoffs: Two runtimes and a relay add operations; delivery may repeat; a SQLx transaction alone does not atomically submit to Redis.
Checks before transfer
- Crash after the domain commit but before relay delivery; an intent remains recoverable.
- Crash the relay after submission before marking sent; duplicate delivery produces one logical effect.
- Reconcile ready, scheduled, retry, failed and in-flight old jobs before any worker replacement.
Delivery and recovery
Inspect the source
Define HTTP admission/error behavior, task ownership, shutdown deadline and behavior for unavoidable CPU/blocking operations.
During coexistence
Recommendation: retain old routing while introducing Tower admission bounds and CancellationToken/TaskTracker-based ownership for a coherent endpoint cohort. Benchmark equivalent resource limits and durability before attributing gains to Rust.
Target design
Recommendation: bounded async work, cooperative cancellation, explicit shutdown notification plus tracked task completion, and SQLx pool close. For untrusted/hard-deadline native work, isolate it in a process whose termination contract is tested.
Rollback
Recommendation: return the cohort to the previous service and reconcile persisted outcomes. Do not simply retry every cancelled request; its external effect may already have occurred.
Benefits and prerequisites
Tracked tasks and explicit admission limits make resource use and planned termination easier to reason about.
Requires: Every child task is tracked; limits include waiting work; cancellation-safe I/O and shutdown budget are verified.
Tradeoffs: spawn_blocking closures may continue after timeout; killing a process still requires durable recovery and external-effect reconciliation.
Checks before transfer
- Cancel an overloaded request and verify queue permits/resources are released.
- Send termination during async and blocking work; document tasks that drain and work that cannot be aborted.
- Force a crash after acceptance; verify recovery of persisted intents and honest failure of memory-only work.
Realtime transport
Inspect the source
Preserve subscriptions, authorization revocation, event versions, reconnect behavior and cross-instance fan-out.
During coexistence
Recommendation: keep the existing shared broker while a Rust gateway serves entire room/tenant cohorts; adapters translate existing messages without dual-emitting user effects. Test cross-runtime publication in both directions.
Target design
Recommendation: Axum WebSocket transport and bounded per-client queues. Tokio broadcast is appropriate only for local ephemeral fan-out with explicit Lagged handling; use durable state/event history to resync clients when loss is unacceptable.
Rollback
Recommendation: route the whole cohort back and retain the shared broker/protocol. Resynchronize from authoritative durable records; local channel state cannot be restored from a previous binary.
Benefits and prerequisites
Local bounded fan-out can prevent slow receivers from retaining unbounded memory.
Requires: Overflow/disconnect/resync policy exists; replica routing and shared fan-out remain covered; every broadcast error is handled.
Tradeoffs: Tokio broadcast is process-local; old messages are evicted when capacity is exceeded; it is not a durable cross-node broker.
Checks before transfer
- Force receiver lag and verify Lagged triggers resync or the documented disconnect policy.
- Publish from each runtime and confirm the opposite runtime's subscribers receive the intended event.
- Reconnect after deployment/crash and rebuild state without trusting missing ephemeral frames.
Setup
- Create source in a local development workspace: cargo new service; enter service and commit the generated application Cargo.lock after dependency resolution.
- Run cargo add axum; cargo add tokio --features macros,rt-multi-thread; add required tower-http middleware after selecting compatible releases.
- Add SQLx selecting PostgreSQL, Tokio and a supported TLS configuration from that release; install matching sqlx-cli. Create config validation and tracing subscriber.
- Use a disposable database in DATABASE_URL. Create migrations with sqlx migrate add initial; write SQL and run sqlx migrate run.
- For checked query macros, run cargo sqlx prepare after schema/query changes and commit offline metadata. Implement HTTP, real-database and cancellation tests.
- Introduce Restate only after a separate restart/retry/idempotency proof. Pin toolchain and release targets. These instructions were not executed.
Verification
Run these in the configured application; they are instructions, not recorded passes.
cargo fmt --all -- --check cargo clippy --workspace --all-targets --locked -- -D warnings cargo test --workspace --locked cargo audit sqlx migrate info cargo sqlx prepare --check cargo build --release --locked
Open questions
- Which API/token contract and trusted key-rotation mechanism apply?
- Which target/runtime/TLS and database configurations must ship?
- Does durable work justify a separate Restate service and its operation?
- Which workload, realtime capacity and restore objectives must be met?