|
| 1 | +# Fetching Public APIs in Rust |
| 2 | + |
| 3 | +This guide shows how to fetch public APIs in Rust using `reqwest` and `tokio`. |
| 4 | +We’ll walk through small examples and explain what’s happening. |
| 5 | + |
| 6 | +--- |
| 7 | + |
| 8 | +## 1. Setup |
| 9 | + |
| 10 | +In `Cargo.toml`: |
| 11 | + |
| 12 | +```toml |
| 13 | +[dependencies] |
| 14 | +tokio = { version = "1", features = ["full"] } |
| 15 | +reqwest = { version = "0.11", features = ["json"] } |
| 16 | +serde = { version = "1.0", features = ["derive"] } |
| 17 | +serde_json = "1.0" |
| 18 | +``` |
| 19 | + |
| 20 | +**Explanation:** |
| 21 | +- **tokio**: Async runtime that allows us to write asynchronous code (`async`/`await`). |
| 22 | +- **reqwest**: HTTP client library for making GET/POST requests. |
| 23 | +- **serde** and **serde_json**: For converting JSON data into Rust structs automatically. |
| 24 | + |
| 25 | +--- |
| 26 | + |
| 27 | +## 2. Fetch a Joke |
| 28 | + |
| 29 | +```rust |
| 30 | +use reqwest::Client; |
| 31 | +use serde::Deserialize; |
| 32 | + |
| 33 | +#[derive(Debug, Deserialize)] |
| 34 | +struct Joke { setup: String, punchline: String } |
| 35 | + |
| 36 | +#[tokio::main] |
| 37 | +async fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 38 | + let client = Client::new(); |
| 39 | + let url = "https://official-joke-api.appspot.com/random_joke"; |
| 40 | + |
| 41 | + let joke: Joke = client.get(url).send().await?.json().await?; |
| 42 | + |
| 43 | + println!("{} — {}", joke.setup, joke.punchline); |
| 44 | + Ok(()) |
| 45 | +} |
| 46 | +``` |
| 47 | + |
| 48 | +**Explanation:** |
| 49 | +- We create a `Client` to make HTTP requests. |
| 50 | +- `client.get(url)` creates a GET request. |
| 51 | +- `.send().await?` sends the request and waits for the response. |
| 52 | +- `.json().await?` converts the JSON into our `Joke` struct automatically using `serde`. |
| 53 | +- We then print the joke’s setup and punchline. |
| 54 | + |
| 55 | +--- |
| 56 | + |
| 57 | +## 3. Weather API Example (OpenWeatherMap) |
| 58 | + |
| 59 | +You can sign up for a free API key at [https://openweathermap.org/](https://openweathermap.org/). |
| 60 | + |
| 61 | +```rust |
| 62 | +use reqwest::Client; |
| 63 | +use serde::Deserialize; |
| 64 | + |
| 65 | +#[derive(Debug, Deserialize)] |
| 66 | +struct WeatherResponse { |
| 67 | + name: String, |
| 68 | + main: Main, |
| 69 | + weather: Vec<Weather>, |
| 70 | +} |
| 71 | + |
| 72 | +#[derive(Debug, Deserialize)] |
| 73 | +struct Main { temp: f64 } |
| 74 | + |
| 75 | +#[derive(Debug, Deserialize)] |
| 76 | +struct Weather { description: String } |
| 77 | + |
| 78 | +#[tokio::main] |
| 79 | +async fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 80 | + let api_key = "YOUR_API_KEY"; // from OpenWeather |
| 81 | + let city = "London"; |
| 82 | + let url = format!( |
| 83 | + "https://api.openweathermap.org/data/2.5/weather?q={}&appid={}&units=metric", |
| 84 | + city, api_key |
| 85 | + ); |
| 86 | + |
| 87 | + let client = Client::new(); |
| 88 | + let resp: WeatherResponse = client.get(&url).send().await?.json().await?; |
| 89 | + |
| 90 | + println!("Weather in {}: {}°C, {}", |
| 91 | + resp.name, |
| 92 | + resp.main.temp, |
| 93 | + resp.weather.first().map(|w| &w.description).unwrap_or(&"No data".to_string()) |
| 94 | + ); |
| 95 | + |
| 96 | + Ok(()) |
| 97 | +} |
| 98 | +``` |
| 99 | + |
| 100 | +**Explanation:** |
| 101 | +- The `WeatherResponse` struct matches the JSON structure returned by OpenWeather. |
| 102 | +- We use `format!` to insert the city name and API key into the URL. |
| 103 | +- The `units=metric` parameter tells the API to return Celsius temperatures. |
| 104 | +- After parsing the JSON, we display the city name, temperature, and weather description. |
| 105 | +- Always store your API key in an environment variable in real apps for security. |
| 106 | + |
| 107 | +--- |
| 108 | + |
| 109 | +## 4. Fetch a Random Cat Fact |
| 110 | + |
| 111 | +```rust |
| 112 | +use reqwest::Client; |
| 113 | +use serde::Deserialize; |
| 114 | + |
| 115 | +#[derive(Debug, Deserialize)] |
| 116 | +struct CatFact { fact: String } |
| 117 | + |
| 118 | +#[tokio::main] |
| 119 | +async fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 120 | + let client = Client::new(); |
| 121 | + let url = "https://catfact.ninja/fact"; |
| 122 | + |
| 123 | + let fact: CatFact = client.get(url).send().await?.json().await?; |
| 124 | + println!("Cat fact: {}", fact.fact); |
| 125 | + Ok(()) |
| 126 | +} |
| 127 | +``` |
| 128 | + |
| 129 | +**Explanation:** |
| 130 | +- This example is almost identical to the joke example but uses another API. |
| 131 | +- The API returns JSON with a single field, `fact`. |
| 132 | +- This is perfect for learning because you can easily change the URL and struct to fetch other simple APIs. |
| 133 | + |
| 134 | +--- |
| 135 | + |
| 136 | +## General Notes |
| 137 | + |
| 138 | +- **Error handling**: In production, always check if the request succeeded before parsing. Use `.error_for_status()` to catch HTTP errors. |
| 139 | +- **Performance**: Reuse the same `Client` across requests instead of creating a new one each time. |
| 140 | +- **Security**: Never hardcode API keys in your source code. Use environment variables or secure config files. |
| 141 | +- **Data mapping**: Struct field names must match the JSON keys or use `#[serde(rename = "json_key_name")]`. |
0 commit comments