reqwest & sqlx
reqwest is the ergonomic async HTTP client. sqlx is an async, compile-time-checked SQL toolkit. Together they cover most service IO boundaries.
Recipe
[dependencies]
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "postgres", "macros"] }
serde = { version = "1.0", features = ["derive"] }use serde::Deserialize;
#[derive(Deserialize)]
struct Health { status: String }
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let health: Health = reqwest::get("https://httpbin.org/status/200")
.await?
.error_for_status()?
.json()
.await
.unwrap_or(Health { status: "ok".into() });
println!("{}", health.status);
Ok(())
}When to reach for this:
- Microservices calling other HTTP APIs
- Postgres/MySQL/SQLite with async pools
- Axum handlers needing DB + outbound HTTP
Working Example
use sqlx::PgPool;
async fn count_users(pool: &PgPool) -> sqlx::Result<i64> {
let row: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM users")
.fetch_one(pool)
.await?;
Ok(row.0)
}async fn notify_webhook(url: &str, body: &str) -> reqwest::Result<()> {
reqwest::Client::new()
.post(url)
.header("content-type", "application/json")
.body(body.to_string())
.send()
.await?
.error_for_status()?;
Ok(())
}What this demonstrates:
query_asmaps rows to tuples or structserror_for_statusturns 4xx/5xx into errors- Both integrate with Tokio
Deep Dive
sqlx Compile-Time Check
// Requires DATABASE_URL at build for query! macro
let user = sqlx::query_as!(User, "SELECT id, email FROM users WHERE id = $1", id)
.fetch_one(pool)
.await?;Use offline mode (sqlx prepare) in CI when the DB is not available at compile time.
Cross-Links
Gotchas
- Default TLS features - prefer
rustls-tlsfor reproducible builds. Fix: disable default OpenSSL unless required. - Connection pool per request - exhausts DB. Fix: one
PgPoolin app state. - reqwest Client per call - loses keep-alive. Fix: reuse
ClientfromOnceLockor Axum state. - sqlx without migrations - schema drift. Fix:
sqlx migratein deploy pipeline. - Large JSON bodies - memory spikes. Fix: streaming with
bytes_stream.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
hyper directly | Custom HTTP | Typical REST clients |
sea-orm | ORM layer on sqlx | Raw SQL preference |
tokio-postgres | Lower-level control | Want compile-time queries |
FAQs
reqwest blocking client?
Exists for sync CLIs. Async client is default for Tokio services.
sqlx with SQLite?
Enable sqlite feature. Great for tests and edge deployments.
Timeouts?
Set Client::builder().timeout(...) and sqlx acquire_timeout on pool options.
Connection string secrets?
Load DATABASE_URL from env at runtime, never commit credentials.
Related
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.