Introduction: Why Rust is an excellent choice for HTTP requests
\nRust 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.
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\nInstallation and project setup
\nThe first step is creating a new project and adding the reqwest dependency with support for asynchrony and JSON.
cargo new rust_http_client\ncd rust_http_client\nOpen Cargo.toml and add:
[dependencies]\nreqwest = { version = "0.12", features = ["json"] }\ntokio = { version = "1", features = ["full"] }\nserde = { version = "1", features = ["derive"] }\nserde_json = "1"\nLet's explain each dependency:
\n- \n
- reqwest — the main HTTP client. The
jsonflag enables serialization/deserialization support. \n - tokio — Rust's asynchronous runtime. HTTP requests in
reqwestare asynchronous by default. \n - serde / serde_json — for working with JSON (structures, serialization). \n
Now everything is ready for writing code.
\n\nBasic GET request: getting data from the server
\nLet'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.
\nuse 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}\nWhat'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. \nresponse.status()returns the HTTP status (e.g., 200 OK). \nresponse.text().await?extracts the response body as a string. \n
Run the code with the command cargo run. You will see a JSON object with the post data.
Adding headers and query parameters
\nIn real applications, you often need to pass headers (e.g., API key) or query parameters. Here's how to do it:
\nuse 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(¶ms)\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}\nKey points:
\n- \n
- We create a
Client— this is a reusable HTTP client with a connection pool. Always use Client instead of directreqwest::getcalls 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