# Rust service

Services where runtime control and explicit resource handling matter.

Axum · Tokio · SQLx · PostgreSQL

[Complete guide (Markdown)](/stacks/rust-service/guide.md) · [Structured record (JSON)](/stacks/rust-service/record.json)

Original source review: 2026-10-10. Core guidance rechecked: 2026-10-13. [Check scope](/research-freshness.json). 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.

- [https://docs.rs/axum/latest/axum/](https://docs.rs/axum/latest/axum/)

- [https://tokio.rs/](https://tokio.rs/)

### 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.

- [https://docs.rs/sqlx/latest/sqlx/](https://docs.rs/sqlx/latest/sqlx/)

- [https://github.com/transact-rs/sqlx/blob/main/sqlx-cli/README.md](https://github.com/transact-rs/sqlx/blob/main/sqlx-cli/README.md)

### Configuration: Serde-backed settings + config

Typed settings plus structured events/spans.

Set precedence explicitly; validate semantics; install a tracing subscriber; never log secrets.

- [https://docs.rs/config/latest/config/](https://docs.rs/config/latest/config/)

- [https://doc.rust-lang.org/std/env/fn.var.html](https://doc.rust-lang.org/std/env/fn.var.html)

- [https://docs.rs/tracing/latest/tracing/](https://docs.rs/tracing/latest/tracing/)

### Application logging: tracing + configured subscriber

Typed settings plus structured events/spans.

Set precedence explicitly; validate semantics; install a tracing subscriber; never log secrets.

- [https://docs.rs/config/latest/config/](https://docs.rs/config/latest/config/)

- [https://doc.rust-lang.org/std/env/fn.var.html](https://doc.rust-lang.org/std/env/fn.var.html)

- [https://docs.rs/tracing/latest/tracing/](https://docs.rs/tracing/latest/tracing/)

### 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.

- [https://doc.rust-lang.org/cargo/commands/cargo-test.html](https://doc.rust-lang.org/cargo/commands/cargo-test.html)

- [https://github.com/rust-lang/rustfmt](https://github.com/rust-lang/rustfmt)

- [https://doc.rust-lang.org/clippy/usage.html](https://doc.rust-lang.org/clippy/usage.html)

- [https://rustsec.org/](https://rustsec.org/)

- [https://docs.rs/assert_cmd/latest/assert_cmd/](https://docs.rs/assert_cmd/latest/assert_cmd/)

### 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.

- [https://docs.rs/restate-sdk/latest/restate_sdk/](https://docs.rs/restate-sdk/latest/restate_sdk/)

### 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.

- [https://doc.rust-lang.org/cargo/commands/cargo-build.html](https://doc.rust-lang.org/cargo/commands/cargo-build.html)

### 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.

- [https://docs.rs/jsonwebtoken/latest/jsonwebtoken/](https://docs.rs/jsonwebtoken/latest/jsonwebtoken/)

### 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.

- [https://docs.rs/lettre/latest/lettre/](https://docs.rs/lettre/latest/lettre/)

### 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.

- [https://docs.aws.amazon.com/sdk-for-rust/latest/dg/rust_s3_code_examples.html](https://docs.aws.amazon.com/sdk-for-rust/latest/dg/rust_s3_code_examples.html)

### 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.

- [https://docs.rs/axum/latest/axum/extract/ws/index.html](https://docs.rs/axum/latest/axum/extract/ws/index.html)

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.

[docs.rs →](https://docs.rs/axum/latest/axum/)
[docs.rs →](https://docs.rs/tower/latest/tower/limit/index.html)
[tokio.rs →](https://tokio.rs/tokio/topics/shutdown)
[tokio.rs →](https://tokio.rs/tokio/tutorial/channels)
[docs.rs →](https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html)
[docs.rs →](https://docs.rs/sqlx/latest/sqlx/macro.query.html)
[docs.rs →](https://docs.rs/sqlx/latest/sqlx/struct.Pool.html)
[github.com →](https://github.com/transact-rs/sqlx/blob/main/sqlx-cli/README.md)
[docs.rs →](https://docs.rs/tokio/latest/tokio/sync/broadcast/index.html)
[riverqueue.com →](https://riverqueue.com/docs/ruby)
[redis.io →](https://redis.io/docs/latest/develop/pubsub/)

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.

[docs.rs →](https://docs.rs/sqlx/latest/sqlx/macro.query.html)
[docs.rs →](https://docs.rs/sqlx/latest/sqlx/struct.Pool.html)
[github.com →](https://github.com/transact-rs/sqlx/blob/main/sqlx-cli/README.md)

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.

[tokio.rs →](https://tokio.rs/tokio/tutorial/channels)
[docs.rs →](https://docs.rs/sqlx/latest/sqlx/macro.query.html)
[riverqueue.com →](https://riverqueue.com/docs/ruby)

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.

[docs.rs →](https://docs.rs/tower/latest/tower/limit/index.html)
[tokio.rs →](https://tokio.rs/tokio/topics/shutdown)
[docs.rs →](https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html)
[docs.rs →](https://docs.rs/sqlx/latest/sqlx/struct.Pool.html)

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.

[docs.rs →](https://docs.rs/axum/latest/axum/)
[docs.rs →](https://docs.rs/tokio/latest/tokio/sync/broadcast/index.html)
[redis.io →](https://redis.io/docs/latest/develop/pubsub/)

[Read the migration workflow →](/rewrite/guide.md)

## 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?

## Sources

- [Axum](https://docs.rs/axum/latest/axum/)

- [Tokio](https://tokio.rs/)

- [SQLx](https://docs.rs/sqlx/latest/sqlx/)

- [SQLx CLI](https://github.com/transact-rs/sqlx/blob/main/sqlx-cli/README.md)

- [Rust config crate](https://docs.rs/config/latest/config/)

- [Rust environment variables](https://doc.rust-lang.org/std/env/fn.var.html)

- [Rust tracing](https://docs.rs/tracing/latest/tracing/)

- [Cargo test](https://doc.rust-lang.org/cargo/commands/cargo-test.html)

- [rustfmt](https://github.com/rust-lang/rustfmt)

- [Clippy usage](https://doc.rust-lang.org/clippy/usage.html)

- [RustSec](https://rustsec.org/)

- [assert_cmd](https://docs.rs/assert_cmd/latest/assert_cmd/)

- [Restate Rust SDK](https://docs.rs/restate-sdk/latest/restate_sdk/)

- [Cargo build](https://doc.rust-lang.org/cargo/commands/cargo-build.html)

- [jsonwebtoken API](https://docs.rs/jsonwebtoken/latest/jsonwebtoken/)

- [lettre documentation](https://docs.rs/lettre/latest/lettre/)

- [AWS Rust S3 examples](https://docs.aws.amazon.com/sdk-for-rust/latest/dg/rust_s3_code_examples.html)

- [Axum WebSocket extractor documentation](https://docs.rs/axum/latest/axum/extract/ws/index.html)

---

Canonical page: https://stack.menu/stacks/rust-service/
