Ne perds pas une semaine Ă Ă©valuer 40 crates. Voici la stack backend 2026 tranchĂ©e que jâutilise pour chaque API Axum.
CĆur#
axum 0.8: router. Extracteurs et middlewares Tower. Ă associer avectokio 1.xfull. Le cĂąblage complet est dĂ©taillĂ© dans le guide API REST Axum + Postgres.tokio: runtime.rt-multi-thread,macros,signal(graceful shutdown !),time.serde+serde_json: DTOs.#[serde(rename_all="camelCase")]Ă la frontiĂšre API,snake_caseĂ lâintĂ©rieur.
Données#
sqlx 0.9(runtime-tokio,tls-rustls-ring-webpki,postgres,macros,migrate) : Postgres async avec requĂȘtes vĂ©rifiĂ©es Ă la compilation.query!attrape les fautes SQL au build. Attention en migrant depuis 0.8 : la featureruntime-tokio-rustlsnâexiste plus, le runtime et le TLS se dĂ©clarent sĂ©parĂ©ment.redis 0.26/deadpool-redis: cache, rate limit et sessions. Pas de cache dans un HashMap partagĂ© entre instances.refineryousqlx::migrate!: migrations versionnĂ©es, jouĂ©es au boot. Jamais deCREATE TABLEĂ la main en prod.
Plomberie HTTP#
tower-http:TraceLayer,CorsLayer,CompressionLayer,RequestIdLayer,TimeoutLayer. Il te faut les quatre en prod. Depuis la 0.6,TimeoutLayer::newest dĂ©prĂ©ciĂ© au profit deTimeoutLayer::with_status_code, qui te laisse choisir le code renvoyĂ© au client.utoipa+utoipa-swagger-ui: OpenAPI gĂ©nĂ©rĂ© depuis le code. Tes Ă©quipes front vont tâadorer.validator(derive) :#[validate(length, email, range)]sur les DTOs dâentrĂ©e. Rejette les donnĂ©es pourries Ă la frontiĂšre.
Observabilité#
tracing+tracing-subscriber(env-filter,json) : logs structurés. JSON en prod, joli en dev.opentelemetry+tracing-opentelemetry: traces vers Jaeger ou Tempo quand les logs ne suffisent plus.metrics+metrics-exporter-prometheus:/metricspour le RPS, les histogrammes de latence et les stats du pool.
Robustesse#
thiserror+anyhow:thiserrorpour les erreurs dâAPI,anyhowpour les binaires et le dĂ©marrage. Un seulAppErrormappĂ© vers HTTP, placĂ© danssrc/error.rs.backonoutokio-retry: backoff exponentiel pour les appels aval. Pas de boucles de retry Ă la main.reqwest(rustls-tls,json) : HTTP sortant. Mets des timeouts ! Par dĂ©faut câest infini, et câest un tueur en prod â câest exactement ce qui plombait ma p99.
Mon extrait Cargo par défaut#
axum = "0.8"
axum-extra = { version = "0.10", features = ["cookie"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sqlx = { version = "0.9", default-features = false, features = ["runtime-tokio", "tls-rustls-ring-webpki", "postgres", "macros", "migrate", "uuid", "chrono"] }
tower-http = { version = "0.6", features = ["full"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
thiserror = "2"
anyhow = "1"
validator = { version = "0.20", features = ["derive"] }
utoipa = { version = "5", features = ["axum_extras"] }Les versions, au 17 septembre 2026#
Relevées sur crates.io le jour de la mise à jour de cet article, et toutes
utilisées par le template du site, qui compile en intégration
continue. Si tu lis ça dans six mois, vérifie : cargo add <crate> te dira la
vérité mieux que moi.
| Crate | Version | Ă quoi faire attention |
|---|---|---|
axum | 0.8.9 | ParamĂštres de chemin en {id} depuis la 0.8 |
axum-extra | 0.12 | Suit Axum de prÚs, vérifie la compatibilité à chaque bump |
tokio | 1.53 | Stable depuis des années, full en dev, features ciblées en prod |
sqlx | 0.9 | runtime-tokio-rustls supprimée, voir plus bas |
tower-http | 0.7 | TimeoutLayer::new déprécié |
serde | 1.0.229 | Rien Ă signaler, et câest trĂšs bien |
tracing-subscriber | 0.3.23 | env-filter et json ne sont pas par défaut |
thiserror | 2.0 | La 2.0 a durci quelques rĂšgles de #[from] |
validator | 0.21 | LâAPI bouge Ă chaque version mineure |
jsonwebtoken | 11 | Grosse refonte depuis la 9 |
argon2 | 0.6 | SaltString a disparu, voir plus bas |
rand | 0.9 | rand::rng() remplace thread_rng() |
Trois migrations qui cassent, et leur correctif#
Ce sont celles qui mâont coĂ»tĂ© du temps en remontant le template Ă jour.
SQLx : le runtime et le TLS se déclarent séparément#
La feature runtime-tokio-rustls nâexiste plus en 0.9. Un cargo add qui la
mentionne échoue avec unrecognized feature.
# Avant (0.8)
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "postgres"] }
# AprĂšs (0.9)
sqlx = { version = "0.9", default-features = false, features = [
"runtime-tokio", "tls-rustls-ring-webpki", "postgres", "macros", "migrate",
] }
Le default-features = false compte : les features par défaut de SQLx 0.9
incluent any, macros, migrate et json, plus large que nécessaire pour
une API qui ne parle quâĂ Postgres.
argon2 : le sel se génÚre tout seul#
En 0.5, il fallait construire un SaltString et le passer au hasher. En 0.6,
hash_password prend le mot de passe et rien dâautre : le sel vient de la
feature getrandom, active par défaut.
// argon2 0.6
use argon2::password_hash::phc::PasswordHash;
use argon2::{Argon2, PasswordHasher, PasswordVerifier};
let hash = Argon2::default()
.hash_password(password.as_bytes())?
.to_string();
Tout exemple qui construit un SaltString::generate(&mut OsRng) est écrit pour
la 0.5. Il ne compile plus, et lâerreur (no SaltString in the root) nâoriente
pas vers la bonne solution.
tower-http : le timeout choisit son code de retour#
TimeoutLayer::new est déprécié en 0.7 au profit de
TimeoutLayer::with_status_code, qui rend explicite ce que reçoit le client
quand la requĂȘte expire.
.layer(TimeoutLayer::with_status_code(
StatusCode::REQUEST_TIMEOUT,
Duration::from_secs(10),
))Ce que jâĂ©vite (pour des API)#
- Les gros ORM magiques : les requĂȘtes SQLx explicites scalent mieux pour du backend
async-stdpour des serveurs en 2026 : Tokio a gagnĂ©, lâĂ©cosystĂšme suit- Rouler ta propre auth JWT : prends
jsonwebtoken+axum-loginou une auth externalisĂ©e (Clerk/Auth0) pour dĂ©marrer. Si tu la gĂšres toi-mĂȘme, suis lâauth JWT dans Axum comme il faut : refresh tokens hashĂ©s, rotation, middleware
Tu veux tout ça déjà cùblé ? Le template Axum du site utilise exactement ces crates, avec le Dockerfile et les tests. Il compile, la CI le vérifie à chaque commit.