The reqwest crate allows you to make HTTP requests in Rust with ease. You can perform GET, POST, and other HTTP methods, handle JSON responses, set headers, and deal with networking errors, all using an ergonomic API similar to popular libraries in other languages.
When I first tried to call an API in Rust, I thought I would need to deal with low-level sockets. Then I found reqwest, and it felt just like using fetch in JavaScript or axios. It was easy to make a GET request and parse JSON into a Rust struct. What impressed me was how type-safe and error-aware everything felt.
This article will show you how to perform HTTP requests using reqwest, handle responses, manage headers, send JSON data, and deal with common errors.
Add reqwest and tokio to Your Project
In Cargo.toml, add:
[dependencies]
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
Because reqwest is asynchronous, you need tokio to run the async functions.
A Simple GET Request
use reqwest;
use tokio;
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let response = reqwest::get("https://httpbin.org/get").await?;
println!("Status: {}", response.status());
let body = response.text().await?;
println!("Body: {}", body);
Ok(())
}
This makes a basic GET request, prints the status code and the response body.
Sending Query Parameters
Use reqwest::Client to build requests:
use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let client = Client::new();
let res = client
.get("https://httpbin.org/get")
.query(&[("name", "rust"), ("lang", "en")])
.send()
.await?;
println!("{}", res.text().await?);
Ok(())
}
This sends a GET request with query parameters like ?name=rust&lang=en.
Sending POST Requests with JSON
You can send structured data using .json(&your_data):
use serde::Serialize;
#[derive(Serialize)]
struct Payload {
name: String,
age: u8,
}
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let payload = Payload {
name: "Rustacean".into(),
age: 5,
};
let client = reqwest::Client::new();
let res = client
.post("https://httpbin.org/post")
.json(&payload)
.send()
.await?;
println!("Response: {}", res.text().await?);
Ok(())
}
You need serde for serialization. This is covered in our upcoming article on JSON with Serde.
Setting Custom Headers
You can set headers like this:
use reqwest::header;
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let client = reqwest::Client::new();
let res = client
.get("https://httpbin.org/headers")
.header(header::USER_AGENT, "MyRustApp/1.0")
.send()
.await?;
println!("{}", res.text().await?);
Ok(())
}
This lets you set content types, authorization tokens, user agents, and more.
Handling Errors Gracefully
All network calls return a Result. You can handle them using match:
let result = reqwest::get("https://invalid.url").await;
match result {
Ok(response) => println!("Status: {}", response.status()),
Err(e) => eprintln!("Request failed: {}", e),
}
This keeps your program from crashing and provides useful feedback.
Parsing JSON Responses
If the API returns JSON, you can deserialize it into a struct:
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct ApiResponse {
origin: String,
}
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let response = reqwest::get("https://httpbin.org/get").await?;
let json: ApiResponse = response.json().await?;
println!("{:?}", json);
Ok(())
}
Rust will enforce correct field types and structure, catching errors at compile time or when deserializing.
Timeouts and Retry Logic
Set timeouts with the ClientBuilder:
use std::time::Duration;
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(5))
.build()?;
This prevents hanging forever on slow servers.
For retries, you can wrap your request in a loop or use a crate like retry.
Summary
The reqwest crate makes HTTP requests in Rust simple and reliable. You can send and receive JSON, use headers, manage errors, and build robust network clients. Combined with serde, it becomes a powerful tool for building web-connected applications.
Rust Intermediate Concepts
- Generic Types in Functions and Structs
- Implementing Traits for Custom Behavior in Rust
- Trait Bounds and Constraints in Rust
- Lifetimes in Rust
- Using Closures (Anonymous Functions) in Rust
- The Iterator Trait and .next() in Rust
- Higher Order Iterator Methods: map, filter, and fold in Rust
- Using impl Trait for Simplicity in Rust
- Advanced Collections: HashSet and BTreeMap in Rust
- Custom Error Types and the Error Trait in Rust
- Option and Result Combinators in Rust
- Writing Unit Tests in Rust
- Integration Testing in Rust
- Logging in Rust with the log Crate
- Cargo Tips and Tricks for Rust Projects
- CLI Argument Parsing with Clap in Rust
- File I/O and File System Operations in Rust
- Running External Commands in Rust
- Make HTTP Requests in Rust with Reqwest
- JSON Serialization and Deserialization in Rust with Serde
- Building a Weather CLI in Rust
- Date and Time Handling in Rust with Chrono
- Using Regular Expressions in Rust with the regex Crate
- Memory Management in Rust
- Understanding Borrow Checker Errors in Rust
- Interacting with Databases in Rust
- Building a Todo List CLI in Rust with SQLite
- Concurrency in Rust