Tutorial
API fetching

API fetching

The goal is to use the PokeAPI to list all the Pokémon of the first generation (the best one, btw) and then reserve a dynamic page for each one separately.

Fetch all the Pokémon

To start, let’s fetch all of them from the root page. Since we want to render them on the server side, we are going to need to implement the logic in the index.rs file.

Clear the index.rs file and paste:

// src/routes/index.rs
use serde::{Deserialize, Serialize};
use reqwest::Client;
use tuono_lib::{Props, Request, Response};

const ALL_POKEMON: &str = "https://pokeapi.co/api/v2/pokemon?limit=151";

#[derive(Debug, Serialize, Deserialize)]
struct Pokemons {
    results: Vec<Pokemon>,
}

#[derive(Debug, Serialize, Deserialize)]
struct Pokemon {
    name: String,
    url: String,
}

#[tuono_lib::handler]
async fn get_all_pokemons(_req: Request, fetch: Client) -> Response {
    return match fetch.get(ALL_POKEMON).send().await {
        Ok(res) => {
            let data = res.json::<Pokemons>().await.unwrap();
            Response::Props(Props::new(data))
        }
        Err(_err) => Response::Props(Props::new("{}")),
    };
}

The first argument is always the request req: Request which contains all the request's data like the query parameters and the HTTP headers. The rest of the arguments represents the ApplicationState and are optional.

The terminal will complain now for two reasons:

  1. We don't have imported any reqwest crate
  2. The second argument fetch: Client has not been defined yet as global state.

Let's fix these in the next section.

Application state

Compared to the common Javascript runtimes, Tuono is fast because only the features you need for your project will be loaded.

You can load them in the ApplicationState of your app inside the ./src/app.rs file. This is the file that will be executed just once at the very beginning of your application.

For the tutorial we will use Reqwest which is one of the most famous HTTP library.

To install it just run in your terminal:

cargo add reqwest

A new entry has just been added to your Cargo.toml file.

[package]
name = "tuono"
version = "0.0.1"
edition = "2021"

[[bin]]
name = "tuono"
path = ".tuono/main.rs"

[dependencies]
tuono_lib = "0.14.0" # the version might be different
serde = { version = "1.0.202", features = ["derive"] }
++ reqwest = "0.12.9" # the version might be different

The Cargo.toml is the manifest file of your application, in which you handle Rust's dependencies (similarly as the package.json for Javascript).

Now let's define the ApplicationState in the ./src/app.rs file.

// Import here the just added reqwest library
use reqwest::Client;

#[derive(Clone)]
// Extend this struct with the feature you will need for your application
pub struct ApplicationState {
    // This will be available to all your route handlers
    pub fetch: Client,
}

pub fn main() -> ApplicationState {
    let fetch = Client::new();
    return ApplicationState { fetch };
}

Now the fetch: Client argument is available in the above defined handler, and the terminal should not complain anymore. Let's see in the next section how to show the fetched data on the browser.

Handling the page UI

Now the Pokémon are correctly fetched and hydrated on the client side, so we can actually use them. Clear the index.tsx file and paste:

// src/routes/index.tsx
import type { JSX } from 'react'
import type { TuonoProps } from 'tuono'

interface IndexProps {
  results: Array<{ name: string; url: string }>
}

export default function IndexPage({
  data,
}: TuonoProps<IndexProps>): JSX.Element | null {
  if (!data?.results) return null

  return (
    <>
      <header className="header">
        <a
          href="https://crates.io/crates/tuono"
          target="_blank"
          rel="noreferrer"
        >
          Crates
        </a>
        <a
          href="https://www.npmjs.com/package/tuono"
          target="_blank"
          rel="noreferrer"
        >
          Npm
        </a>
      </header>
      <div className="title-wrap">
        <h1 className="title">
          TU<span>O</span>NO
        </h1>
        <div className="logo">
          <img src="rust.svg" className="rust" />
          <img src="react.svg" className="react" />
        </div>
      </div>
      <ul style={{ flexWrap: 'wrap', display: 'flex', gap: 10 }}>
        {data.results.map((pokemon, i) => (
          <li key={i + 1}>{pokemon.name}</li>
        ))}
      </ul>
    </>
  )
}

Refresh the browser now! A bit ugly, but all the Pokémon are finally printed on screen!

Edit page