Make HTTP Requests in Rust with Reqwest

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.