+ >
+
+
+
+
+
+
+
+
+
+
void }): JSX.Element {
+ Tutorial - API fetching
+
+
+import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
+
+
+
+
+
+
+# API fetching
+
+The goal is to use the [PokeAPI](https://pokeapi.co/docs/v2) to list all the pokemons of the first generation (the best one btw) and then reserve a \_\_ page for each one separately.
+
+## Fetch all the pokemons
+
+To start let’s fetch all of them in the root page; since we want to render them on the server side we gonna need to implement the logic in the `index.rs` file.
+
+Clear the `index.rs` file and paste:
+
+```rust
+// src/routes/index.rs
+use serde::{Deserialize, Serialize};
+use tuono_lib::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,
+}
+
+#[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::().await.unwrap();
+ Response::Props(Props::new(data))
+ }
+ Err(_err) => Response::Props(Props::new("{}")),
+ };
+}
+```
+
+Now the pokemons are correctly fetched and hydrated on the client side so we can actually use them. Clear the `index.tsx` file and paste:
+
+```tsx
+// src/routes/index.tsx
+import type { TuonoProps } from 'tuono'
+
+interface Pokemon {
+ name: string
+}
+
+interface IndexProps {
+ results: Pokemon[]
+}
+
+export default function IndexPage({
+ data,
+}: TuonoProps): JSX.Element {
+ if (!data?.results) {
+ return <>>
+ }
+
+ return (
+ <>
+
+
+ Crates
+
+
+ Npm
+
+
+
+ >
+ )
+}
+```
+
+Refresh now the browser! A bit ugly but all the pokemons are finally printed on screen!
diff --git a/apps/documentation/src/routes/documentation/tutorial/components.mdx b/apps/documentation/src/routes/documentation/tutorial/components.mdx
new file mode 100644
index 00000000..45c84230
--- /dev/null
+++ b/apps/documentation/src/routes/documentation/tutorial/components.mdx
@@ -0,0 +1,129 @@
+import { Head } from 'tuono'
+
+
+ Tutorial - Components
+
+
+import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
+
+
+
+
+
+
+# Components
+
+## Creating a stand-alone component
+
+Let’s then create the button needed for displaying the list of pokemons.
+
+Create the following file `src/components/PokemonLink.tsx` and fill the content with:
+
+```tsx
+// src/components/PokemonLink.tsx
+import { Link } from 'tuono'
+
+interface Pokemon {
+ name: string
+}
+
+export default function PokemonLink({
+ pokemon,
+ id,
+}: {
+ pokemon: Pokemon
+ id: number
+}): JSX.Element {
+ return (
+
+ {pokemon.name}
+
+
+ )
+}
+```
+
+Now that the link is done let’s import it in the `index.tsx` file
+
+```diff
+// src/routes/index.tsx
+
+++ import PokemonLink from '../components/PokemonLink'
+
+// ...
+
+// ...
+```
+
+Now the links work. Clicking on any of them we get redirected to the 404 page because we haven’t yet implemented the `pokemons/[pokemon]` page.
+As previously said CSS modules are enabled out of the box so let’s make those links a little bit nicer.
+
+Create alongside the `PokemonLink.tsx` component the CSS module `PokemonLink.module.css` and copy the following content into it:
+
+```css
+/* src/components/PokemonLink.module.css */
+
+.link {
+ width: 100%;
+ max-width: 216px;
+ position: relative;
+ background: white;
+ margin-bottom: 10px;
+ border: solid #f0f0f0 1px;
+ text-decoration: none;
+ color: black;
+ padding: 5px 5px 5px 15px;
+ border-radius: 10px;
+ display: flex;
+ justify-content: space-between;
+ transition: 0.2s;
+ align-items: center;
+}
+
+.link:hover {
+ box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
+}
+
+.link img {
+ width: 70px;
+ background: white;
+ border-radius: 50%;
+}
+```
+
+> 💡 SASS is supported out of the box. Just install the processor in the devDependencies `npm i -D sass` and run again `tuono dev`
+
+Then import the styles into the `PokemonLink` component as following:
+
+```diff
+// src/components/PokemonLink.tsx
+import { Link } from "tuono";
+import type { Pokemon } from "./../types/pokemon";
+++ import styles from './PokemonLink.module.css'
+
+export default function PokemonLink({
+ pokemon,
+ id,
+}: {
+ pokemon: Pokemon;
+ id: number;
+}): JSX.Element {
+ return (
+--
+++
+ {pokemon.name}
+
+
+ );
+}
+```
diff --git a/apps/documentation/src/routes/documentation/tutorial/conclusion.mdx b/apps/documentation/src/routes/documentation/tutorial/conclusion.mdx
new file mode 100644
index 00000000..a2854fd4
--- /dev/null
+++ b/apps/documentation/src/routes/documentation/tutorial/conclusion.mdx
@@ -0,0 +1,24 @@
+import { Head } from 'tuono'
+
+
+ Tutorial - Conclusion
+
+
+import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
+
+
+
+
+
+
+# Conclusion
+
+That’s it! You just created a multi thread full stack application with rust and react.
+
+The project is still under heavy development and many features are not ready yet but
+I hope you got the taste of what is like working with rust and react in the same stack.
+
+As I mentioned in the introduction I'd love to hear what you thought about the framework and the tutorial - feel free to reach me
+at [valerioageno@yahoo.it](mailto:valerioageno@yahoo.it) or in Twitter (X) DMs [@valerioageno](https://twitter.com/valerioageno).
+
+Ciao
diff --git a/apps/documentation/src/routes/documentation/tutorial/dynamic-routes.mdx b/apps/documentation/src/routes/documentation/tutorial/dynamic-routes.mdx
new file mode 100644
index 00000000..b93b13e8
--- /dev/null
+++ b/apps/documentation/src/routes/documentation/tutorial/dynamic-routes.mdx
@@ -0,0 +1,159 @@
+import { Head } from 'tuono'
+
+
+ Tutorial - __ routes
+
+
+import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
+
+
+
+
+
+
+# \_\_ routes
+
+## Create the pokemon route
+
+The homepage is ready. We have the full list of pokemons and they are all links. Great!
+
+Now we want to make those links actually pointing to a real page. Let’s create the \_\_ route.
+
+Create the folder `routes/pokemons` and then create the two files `[pokemon].tsx` and `[pokemon].rs`.
+
+These two will handle every requests that points to `http://localhost:3000/pokemons/bulbasaur..mew`.
+
+Let’s first work on the server side file. Paste into the new `[pokemon].rs` file the following code:
+
+```rust
+// src/routes/pokemons/[pokemon].rs
+use serde::{Deserialize, Serialize};
+use tuono_lib::reqwest::Client;
+use tuono_lib::{Props, Request, Response};
+
+const POKEMON_API: &str = "https://pokeapi.co/api/v2/pokemon";
+
+#[derive(Debug, Serialize, Deserialize)]
+struct Pokemon {
+ name: String,
+ id: u16,
+ weight: u16,
+ height: u16,
+}
+
+#[tuono_lib::handler]
+async fn get_pokemon(req: Request, fetch: Client) -> Response {
+ // The param `pokemon` is defined in the route filename [pokemon].rs
+ let pokemon = req.params.get("pokemon").unwrap();
+
+ return match fetch.get(format!("{POKEMON_API}/{pokemon}")).send().await {
+ Ok(res) => {
+ let data = res.json::().await.unwrap();
+ Response::Props(Props::new(data))
+ }
+ Err(_err) => Response::Props(Props::new("{}"))
+ };
+}
+```
+
+Then let’s work on the frontend. Paste into the `[pokemon].tsx` file the following code:
+
+```tsx
+import { TuonoProps } from 'tuono'
+import PokemonView from '../../components/PokemonView'
+
+export default function Pokemon({ data }: TuonoProps): JSX.Element {
+ return
+}
+```
+
+The browser should complain that the component `PokemonView` does not exist. Let’s create it then!
+
+```tsx
+// components/PokemonView.tsx
+import { Link } from 'tuono'
+import styles from './PokemonView.module.css'
+
+interface Pokemon {
+ name: string
+ id: string
+ weight: number
+ height: number
+}
+
+export default function PokemonView({
+ pokemon,
+}: {
+ pokemon?: Pokemon
+}): JSX.Element {
+ return (
+
+
+ Back
+
+ {pokemon?.name && (
+
+
+
{pokemon.name}
+
+
Weight:
+
{pokemon.weight}lbs
+
+
+
Height:
+
{pokemon.height}ft
+
+
+
+
+ )}
+
+ )
+}
+```
+
+
+
+```css
+/* components/PokemonView.module.css */
+.back-btn {
+ background-color: white;
+ border-radius: 10px;
+ padding: 7px 15px;
+ color: black;
+ text-decoration: none;
+ border: solid #f0f0f0 1px;
+ font-size: 20px;
+}
+
+.back-btn:hover {
+ box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
+}
+
+.pokemon {
+ display: flex;
+ justify-content: space-between;
+ margin-top: 20px;
+}
+
+.name {
+ font-size: 50px;
+ font-weight: 700;
+}
+
+.pokemon img {
+ width: 400px;
+}
+
+.spec {
+ display: flex;
+ font-size: 18px;
+ margin-top: 10px;
+}
+
+.label {
+ font-weight: 700;
+}
+```
diff --git a/apps/documentation/src/routes/documentation/tutorial/error-handling.mdx b/apps/documentation/src/routes/documentation/tutorial/error-handling.mdx
new file mode 100644
index 00000000..8af27efc
--- /dev/null
+++ b/apps/documentation/src/routes/documentation/tutorial/error-handling.mdx
@@ -0,0 +1,102 @@
+import { Head } from 'tuono'
+
+
+ Tutorial - Error handling
+
+
+import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
+
+
+
+
+
+
+# Error handling
+
+With the current setup all the routes always return a `200 Success` http status no matter the response type.
+
+In order to return a more meaningful status code to the browser the `Props` struct can be initialized with also the
+`Props::new_with_status()` method.
+
+Let's see how it works!
+
+```diff
+// src/routes/pokemons/[pokemon].rs
+use serde::{Deserialize, Serialize};
+-- use tuono_lib::reqwest::Client;
+++ use tuono_lib::reqwest::{Client, StatusCode};
+use tuono_lib::{Props, Request, Response};
+
+const POKEMON_API: &str = "https://pokeapi.co/api/v2/pokemon";
+
+#[derive(Debug, Serialize, Deserialize)]
+struct Pokemon {
+ name: String,
+ id: u16,
+ weight: u16,
+ height: u16,
+}
+
+#[tuono_lib::handler]
+async fn get_pokemon(req: Request, fetch: Client) -> Response {
+ // The param `pokemon` is defined in the route filename [pokemon].rs
+ let pokemon = req.params.get("pokemon").unwrap();
+
+ return match fetch.get(format!("{POKEMON_API}/{pokemon}")).send().await {
+ Ok(res) => {
+++ if res.status() == StatusCode::NOT_FOUND {
+++ return Response::Props(Props::new_with_status("{}", StatusCode::NOT_FOUND));
+++ }
+
+ let data = res.json::().await.unwrap();
+ Response::Props(Props::new(data))
+ }
+-- Err(_err) => Response::Props(Props::new(
+++ Err(_err) => Response::Props(Props::new_with_status(
+++ "{}",
+++ StatusCode::INTERNAL_SERVER_ERROR,
+ )),
+ };
+}
+```
+
+
+
+```diff
+// src/routes/index.rs
+use serde::{Deserialize, Serialize};
+-- use tuono_lib::reqwest::Client;
+++ use tuono_lib::reqwest::{Client, StatusCode};
+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,
+}
+
+#[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::().await.unwrap();
+ Response::Props(Props::new(data))
+ }
+-- Err(_err) => Response::Props(Props::new(
+++ Err(_err) => Response::Props(Props::new_with_status(
+++ "{}", // Return empty JSON
+++ StatusCode::INTERNAL_SERVER_ERROR,
+ )),
+ };
+}
+```
+
+If you now try to load a not existing pokemon (`http://localhost:3000/pokemons/tuono-pokemon`) you will
+correctly receive a 404 status code in the console.
diff --git a/apps/documentation/src/routes/documentation/tutorial/index.mdx b/apps/documentation/src/routes/documentation/tutorial/index.mdx
new file mode 100644
index 00000000..9e3405f7
--- /dev/null
+++ b/apps/documentation/src/routes/documentation/tutorial/index.mdx
@@ -0,0 +1,43 @@
+import { Head } from 'tuono'
+
+
+ Tuono - Tutorial
+
+
+import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
+
+
+
+
+
+# Tutorial
+
+This tutorial is meant for giving you a sneak peek of the framework and is intended to evolve during the
+development - be sure to have [installed](/documentation/installation) the [latest version](https://crates.io/crates/tuono).
+
+This tutorial is not meant for people that don't know React - in that case I suggest you to first read the
+[React doc](https://react.dev/); Typescript and Rust knowledge is not a requirement though!
+
+If you prefer to just read the code rather than writing it you can download the finished tutorial project with:
+
+```bash
+$ tuono new tutorial --template tutorial
+```
+
+> I'd love to hear your thoughts about the framework and the tutorial - feel free to reach me at [valerioageno@yahoo.it](mailto:valerioageno@ahoo.it)
+> or on Twitter (X) DM [@valerioageno](https://twitter.com/valerioageno)
+
+## Table of contents
+
+- Project scaffold
+- Start the dev environment
+- The “/” route
+- Tutorial introduction
+- Fetch all the pokemons
+- Create a stand-alone component
+- Create the /pokemons/[pokemon] route
+- Error handling
+- Seo and meta tags
+- Handle redirections
+- Building for production
+- Conclusion
diff --git a/apps/documentation/src/routes/documentation/tutorial/intro.mdx b/apps/documentation/src/routes/documentation/tutorial/intro.mdx
deleted file mode 100644
index e0c82c44..00000000
--- a/apps/documentation/src/routes/documentation/tutorial/intro.mdx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Head } from 'tuono'
-
-
- Tuono - Tutorial intro
-
-
-import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
-
-
-
-
-
-# Tutorial
-
-You can find the tutorial [here](https://github.com/Valerioageno/tuono/blob/main/docs/tutorial.md).
-
-Other tasks have the priority right now. The tutorial will be ported here as soon as possible.
diff --git a/apps/documentation/src/routes/documentation/tutorial/overview.mdx b/apps/documentation/src/routes/documentation/tutorial/overview.mdx
new file mode 100644
index 00000000..c75bf1e9
--- /dev/null
+++ b/apps/documentation/src/routes/documentation/tutorial/overview.mdx
@@ -0,0 +1,66 @@
+import { Head } from 'tuono'
+
+
+ Tutorial - Scaffold new project
+
+
+import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
+
+
+
+
+
+
+# Development setup
+
+## Scaffold a new project
+
+To setup a new fresh project you just need to run the following command:
+
+```
+$ tuono new tuono-tutorial
+```
+
+Get into the project folder and install the dependencies with:
+
+```
+$ npm install
+```
+
+Open it with your favourite code editor.
+
+The project will have the following structure:
+
+```
+├── package.json
+├── public
+├── src
+│ ├── routes
+│ └── styles
+├── Cargo.toml
+├── README.md
+└── tsconfig.json
+```
+
+`public/`: put here all the files you want to be public
+
+`src/routes/`: All the files in this folder are considered routes. All the routes are server side rendered out of the box.
+To add server side capabilities just create a rust file with the same name as the route (i.e. `about.tsx` → `about.rs`).
+
+`src/styles/`: In this folder there is the `global.css` file that stores all the global styles. For the rest of the project you can use
+CSS modules.
+
+## Start the dev environment
+
+To start the development environment you just need to run the following command within the project folder:
+
+```
+$ tuono dev
+```
+
+The first time might take a little bit because it will install all the rust’s dependencies. All the other execution will be pretty quick!
+
+> 💡 The tuono dev development script is currently under strong optimization improvements.
+> In case you face any error delete the cache `.tuono` folder and run it again!
+
+Then open [http://localhost:3000/](http://localhost:3000) on the browser.
diff --git a/apps/documentation/src/routes/documentation/tutorial/production.mdx b/apps/documentation/src/routes/documentation/tutorial/production.mdx
new file mode 100644
index 00000000..d600e0e0
--- /dev/null
+++ b/apps/documentation/src/routes/documentation/tutorial/production.mdx
@@ -0,0 +1,33 @@
+import { Head } from 'tuono'
+
+
+ Tutorial - Building for production
+
+
+import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
+
+
+
+
+
+
+# Production build
+
+The source now is ready to be released. Both server and client have been managed in a unoptimized way
+to easy the development experience. To build the project to the production state just run:
+
+```shell
+$ tuono build
+```
+
+This command just created the final assets within the `out` directory. To run then the prodiction server
+run:
+
+```shell
+$ cargo run --release
+```
+
+Check again [`http://localhost:3000/`](http://localhost:3000/) - This environment now has all the
+optimizations ready to unleash the power of a rust server that seamessly renders a React application!🚀
+
+> Note: The `out` directory is not standalone. You can't rely just on it to run the production server.
diff --git a/apps/documentation/src/routes/documentation/tutorial/redirections.mdx b/apps/documentation/src/routes/documentation/tutorial/redirections.mdx
new file mode 100644
index 00000000..5def6e49
--- /dev/null
+++ b/apps/documentation/src/routes/documentation/tutorial/redirections.mdx
@@ -0,0 +1,49 @@
+import { Head } from 'tuono'
+
+
+ Tutorial - Redirections
+
+
+import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
+
+
+
+
+
+
+# Redirections
+
+## Handle redirections
+
+What if there is a pokemon among all of them that should be considered the GOAT? What
+we are going to do right now is creating a new route `/pokemons/GOAT` that points to the best
+pokemon of the first generation.
+
+First let's create a new route by just creating an new file `/pokemons/GOAT.rs` and pasting the following code:
+
+```rs
+// src/routes/pokemons/GOAT.rs
+use tuono_lib::{reqwest::Client, Request, Response};
+
+#[tuono_lib::handler]
+async fn redirect_to_goat(_: Request, _: Client) -> Response {
+ // Of course the GOAT is mewtwo - feel free to select your favourite 😉
+ Response::Redirect("/pokemons/mewtwo".to_string())
+}
+```
+
+Now let's create the button in the home page to actually point to it!
+
+```diff
+// src/routes/index.tsx
+
+
+```
+
+Now at [http://localhost:3000/](http:/localhost:3000/) you will find a new link at the beginning of the list.
+Click on it and see the application automatically redirecting you to your favourite pokemon's route!
diff --git a/apps/documentation/src/routes/documentation/tutorial/seo.mdx b/apps/documentation/src/routes/documentation/tutorial/seo.mdx
new file mode 100644
index 00000000..a35e49a4
--- /dev/null
+++ b/apps/documentation/src/routes/documentation/tutorial/seo.mdx
@@ -0,0 +1,96 @@
+import { Head } from 'tuono'
+
+
+ Tutorial - SEO and meta tags
+
+
+import Breadcrumbs, { Element } from '../../../components/breadcrumbs'
+
+
+
+
+
+
+# SEO and meta tags
+
+The website now works and the http errors are meaningful but we should also take care to be meaningful
+for the web crawlers. The best way to do it is to enrich the meta tags like the `` and the
+``.
+
+To do so `tuono` exposes also the `` component useful exactly for handling this scenario. Let's update the `/` and the
+`/pokemons/[pokemon]` routes with this.
+
+```diff
+// src/routes/index.tsx
+import type { TuonoProps } from "tuono";
+++ import { Head } from "tuono"
+
+interface Pokemon {
+ name: string
+}
+
+interface IndexProps {
+ results: Pokemon[]
+}
+
+export default function IndexPage({
+ data,
+}: TuonoProps): JSX.Element {
+ if (!data?.results) {
+ return <>>;
+ }
+
+ return (
+ <>
+++
+++ Tuono tutorial
+++
+
+
+ Crates
+
+
+ Npm
+
+
+