How to Fetch a Web API with Rust 🦀
Tags: rust, webdev, tutorial, beginners
Published: 2021-01-18 05:29:46

In this tutorial, I will be teaching you how to fetch a Web API asynchronously in Rust. This tutorial is for developers who know the basics of Rust, have Rust installed, and want to learn about asynchronous programming in Rust. If you don't have Rust installed you can download it here: https://www.rust-lang.org/tools/install.

## What are we building?

In this tutorial, we are doing to be building a CLI stocks app. We would run our program and specify an argument: the company symbol. For example: `cargo run AAPL` would give us the price of the Apple stock. We are going to be using the Finnhub API for this project which is completely free. Let's dive right into it!

## Setting up our project

Let's create a new Rust project and open it up in your favourite code editor (mine is VSCode).

```
cargo new stock-cli
cd stock-cli/
```

Your project should look like this:

```
├── Cargo.toml
└── src
    └── main.rs
```

Let's install our dependencies by typing this under the dependencies section in our `Cargo.toml`. Your `Cargo.toml` should look like this:

```toml
[package]
name = "stock-cli"
version = "0.1.0"
authors = ["Your Name"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
structopt = "0.3.21"
exitfailure = "0.5.1"
reqwest = { version = "0.11.0", features = ["json"] }
serde = "1.0.119"
serde_json = "1.0.61"
serde_derive = "1.0.119"
tokio = { version = "1.0.2", features = ["full"] }
```

I'll explain what these dependencies are along the way. Next, we need our API key. Go to `https://finnhub.io/register` and create an account. Once signed in, go to `https://finnhub.io/dashboard` and you should see your API key. Keep this open, we will use it in this project. With that out of the way, let's get into coding!

## Coding our project

Let's first get the argument that the user passes. We can do that by using the standard library's env module. Your `src/main.rs` should look like this:

```rust
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    println!("{:?}", args);
}
```

In this code, we are importing the standard library's env module, using the `args()` function to collect them, and printing them out in the debug format (`{:?}` means to debug the value). If you run this command: `cargo run AAPL`, should receive this: 

```
["target/debug/stock-cli", "AAPL"]
```

As you can see, the value we are interested in is the 2nd element in the vector or the vector at index 1 (Rust is 0 indexed). Let's handle the scenario where a user doesn't enter an argument:

`src/main.rs`
```rust
use std::env;

fn main() {
    let args: Vec<String> = env::args().collect();
    let mut company: String = "AAPL".to_string();

    if args.len() < 2 {
        println!("Since you didn't specify a company symbol, it is defaulted to AAPL.");
    } else {
        company = args[1].clone();
    }

    println!("{:?}", company);
}
```

As you can see, if the user doesn't enter the company symbol, it defaults to AAPL. Otherwise, the company is the argument they specified. If we run our program without any arguments, it should still work. 

`cargo run`
```
Since you didn't specify a company symbol, it has defaulted to AAPL.
"AAPL"
```

Now, let's fetch the Finnhub API. The API endpoint we are using is <a href="https://finnhub.io/docs/api#quote">/quote</a> which returns the current stock price and some other details. 

`src/main.rs`
```rust
use exitfailure::ExitFailure;
use reqwest::Url;
use serde_derive::{Deserialize, Serialize};
use std::env;

#[derive(Serialize, Deserialize, Debug)]
struct CompanyQuote {
    c: f64,
    h: f64,
    l: f64,
    o: f64,
    pc: f64,
    t: i128,
}

impl CompanyQuote {
    async fn get(symbol: &String, api_key: &String) -> Result<Self, ExitFailure> {
        let url = format!(
            "https://finnhub.io/api/v1/quote?symbol={}&token={}",
            symbol, api_key
        );

        let url = Url::parse(&*url)?;
        let res = reqwest::get(url).await?.json::<CompanyQuote>().await?;

        Ok(res)
    }
}

#[tokio::main]
async fn main() -> Result<(), ExitFailure> {
    let api_key = "YOUR API KEY".to_string();
    let args: Vec<String> = env::args().collect();
    let mut symbol: String = "AAPL".to_string();

    if args.len() < 2 {
        println!("Since you didn't specify a company symbol, it has defaulted to AAPL.");
    } else {
        symbol = args[1].clone();
    }

    let res = CompanyQuote::get(&symbol, &api_key).await?;
    println!("{}'s current stock price: {}", symbol, res.c);

    Ok(())
}
```

Make sure to replace "YOUR API KEY" with the API key in your dashboard. In this code, we are outlining the response which is the struct `CompanyQuote`. In the implementation of `CompanyQuote`, we have an asynchronous function called `get` which returns a Result with a `CompanyQuote` or an error. This function expects the company symbol and API key as parameters. First, we are formatting our URL to contain the symbol and API key. Next, we are parsing the URL and getting it using the reqwest module. We are awaiting the response and then turn it into JSON. The `?` operator just means the same as `unwrap`. Finally, we are returning an Ok with the result inside of it.

Firstly, before the main function, we are putting this: `#[tokio::main]`. That means that we can use await in our main function. Towards the end of the main function, we are getting the company quote and passing the symbol and API key as parameters. We await the response and then (using the `println!` macro) we print out the current stock price. Then we return an `Ok` saying that everything went as planned. 

You might be wondering, what does async and await mean? 

## Async/await

In this section, I will give you a quick overview of `async` and `await`.

Async - allows you to do await
Await - waits for the command to execute before moving on to the next command

Why do we need to wait for the command to execute?

Fetching a web API takes some time, as you can imagine. If we don't wait for the data to be fetched and then do something with the response (like turning it into JSON) we will get an error because the result is a future (waiting to fetch the data). Further reading on async/await in Rust can be found here: https://dev.to/gruberb/explained-how-does-async-work-in-rust-46f8.

## Running the code

Let's run the code by running `cargo run {the company symbol you want the stock price of}` (I did `cargo run TSLA`). As you can see, it works (the stock price is at the time of writing this article)!

```
TSLA's current stock price: 826.16
```

****

I hope you learnt how to fetch an API with Rust and have a general understanding of async and await. More Rust tutorials coming soon! The final code is on GitHub. Thanks for reading!

{% github https://github.com/henryboisdequin/stock-cli %}

**Henry**

<a href="https://buttondown.email/hb">📰 Newsletter</a>
<a href="https://github.com/henryboisdequin">🐱 GitHub</a>
<a href="https://twitter.com/henryboisdequin">🐦 Twitter</a>