HTTP client in Rust: making requests correctly from A to Z

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why Rust is an excellent choice for HTTP requests

\n

Rust is a systems programming language that has gained popularity due to its safety, performance, and lack of a garbage collector. If you are developing a network application, web scraper, or microservice, knowing how to properly make HTTP requests in Rust is an essential skill. In this article, we will break down how to create a full-fledged HTTP client in Rust using the reqwest library, which is the de facto standard for working with HTTP in the Rust ecosystem.

\n

We will go from installing dependencies to error handling and working with JSON. By the end of the article, you will be able to write reliable and fast HTTP clients in Rust that won't let you down in production.

\n\n

Installation and project setup

\n

The first step is creating a new project and adding the reqwest dependency with support for asynchrony and JSON.

\n
cargo new rust_http_client\ncd rust_http_client
\n

Open Cargo.toml and add:

\n
[dependencies]\nreqwest = { version = "0.12", features = ["json"] }\ntokio = { version = "1", features = ["full"] }\nserde = { version = "1", features = ["derive"] }\nserde_json = "1"
\n

Let's explain each dependency:

\n
    \n
  • reqwest — the main HTTP client. The json flag enables serialization/deserialization support.
  • \n
  • tokio — Rust's asynchronous runtime. HTTP requests in reqwest are asynchronous by default.
  • \n
  • serde / serde_json — for working with JSON (structures, serialization).
  • \n
\n

Now everything is ready for writing code.

\n\n

Basic GET request: getting data from the server

\n

Let's start with a simple GET request. We'll create a function that gets data from a public API and prints it to the console.

\n
use reqwest::Error;\n\n#[tokio::main]\nasync fn main() -> Result<(), Error> {\n    let url = "https://jsonplaceholder.typicode.com/posts/1";\n    let response = reqwest::get(url).await?;\n\n    println!("Status: {}", response.status());\n\n    let body = response.text().await?;\n    println!("Response body:\\n{}", body);\n\n    Ok(())\n}
\n

What's happening here:

\n
    \n
  • We use the #[tokio::main] macro to run an asynchronous function.
  • \n
  • reqwest::get(url).await? performs a GET request. The ? operator propagates the error if the request fails.
  • \n
  • response.status() returns the HTTP status (e.g., 200 OK).
  • \n
  • response.text().await? extracts the response body as a string.
  • \n
\n

Run the code with the command cargo run. You will see a JSON object with the post data.

\n\n

Adding headers and query parameters

\n

In real applications, you often need to pass headers (e.g., API key) or query parameters. Here's how to do it:

\n
use reqwest::Client;\nuse std::collections::HashMap;\n\n#[tokio::main]\nasync fn main() -> Result<(), reqwest::Error> {\n    let client = Client::new();\n\n    let mut params = HashMap::new();\n    params.insert("userId", "1");\n\n    let response = client\n        .get("https://jsonplaceholder.typicode.com/posts")\n        .header("Accept", "application/json")\n        .query(&params)\n        .send()\n        .await?;\n\n    let posts: serde_json::Value = response.json().await?;\n    println!("Number of posts: {}", posts.as_array().unwrap().len());\n\n    Ok(())\n}
\n

Key points:

\n
    \n
  • We create a Client — this is a reusable HTTP client with a connection pool. Always use Client instead of direct reqwest::get calls for production code.
  • \n
  • The .header() method adds a header.
  • \n
  • .query() accepts a dictionary of parameters and automatically encodes the URL.
  • \n
  • .json() immediately deser

Blogs

Book Recommendations