Yep, another React framework. But with a difference that backend is written in Rust, Axum. together they create Tuono!
To install Tuono, use the following command:
In today’s tutorial, we’ll go over how to install Rust and Tuono, how to get oriented within the project, and we’ll try setting up an API endpoint that logs data received from a form. You’ll also need to have the following installed Node.js
To install Rust, run this command!
1curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shPro instalaci Tuona použijte tento pÅ™Ãkaz:
1cargo install tuono
2rustup install nightly
3If everything is installed, let's move on to creating the project.
1tuono new example --template with-tailwind
2cd example/
3npm install
4rustup override set nightly
5cargo add serde
6npm i react-hot-toastThe commands above will create the project and install the necessary npm packages. The --template with-tailwind flag downloads the version of Tuono with Tailwind, using the latest version 4. Next, set the project to use the nightly Rust toolchain and install serde for serialization. For toast notifications, install react-hot-toast. Once everything is installed, start the server!
1tuono devAlso, open the project in your IDE so we can play around with it a bit.
Open the tuono.config.ts file and adjust the alias for importing components.
1const config: TuonoConfig = {
2 vite: {
3 alias: {
4 '@': 'src',
5 },
6 plugins: [tailwindcss()],
7 },
8}Folder and file structure
![[object Object]](/_next/image?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fd9iesoru%2Fproduction%2F843513429c518b48eb9c03bdc9be1d45bd437982-338x886.png&w=640&q=75)
In the src folder, you'll find routing, styles, and API endpoints. Just like in Next.js, new routes can be created by making a folder and placing a file named index.tsx inside it. Sometimes, a __layout.tsx file is also needed. Additionally, Tuono introduces a new .rs file — index.rs. This file functions similarly to getServerSideProps in Next.js. It's used to load data from the database and pass it to the frontend.
1use serde::Serialize;
2use tuono_lib::{Props, Request, Response};
3
4#[derive(Serialize)]
5struct MyResponse<'a> {
6 subtitle: &'a str,
7}
8
9#[tuono_lib::handler]
10async fn get_server_side_props(_req: Request) -> Response {
11 Response::Props(Props::new(MyResponse {
12 subtitle: "The react / rust fullstack framework",
13 }))
14}We'll look at more complex data fetching from servers next time. For now, let's create a form that will process and handle our data.
1<form
2 action="/api/formdata"
3 method='POST'
4 className='m-10 flex flex-col space-y-2'>
5 <h1>Sign Up Form</h1>
6 <label htmlFor="name">Name:</label>
7 <input
8 name='name'
9 id='name'
10 type="text"
11 placeholder='Enter your name'
12 className='border px-2 w-96 border-black rounded-xl'/>
13 <label htmlFor="age">Age:</label>
14 <input
15 name='age'
16 id='age'
17 type="number"
18 placeholder='Enter your age'
19 className='border px-2 w-96 border-black rounded-xl'/>
20 <label htmlFor="email">Email:</label>
21 <input
22 name='email'
23 id='email' type="email"
24 placeholder='Enter your email'
25 className='border px-2 w-96 border-black rounded-xl'/>
26 <label htmlFor="password">Password:</label>
27 <input
28 name='password'
29 id='password'
30 type="text"
31 placeholder='Enter your name'
32 className='border px-2 w-96 border-black rounded-xl'/>
33 <button type='submit'>Submit</button>
34 </form>Next, create an api folder inside the src/routes directory, and within the api folder, create a formdata.rs file, which will serve as our endpoint.
In the formdata.rs file, we'll process the data, print it out, and redirect the user back.
1use tuono_lib::{axum::response::Redirect, Request};
2use serde::Deserialize;
3
4#[derive(Deserialize)]
5struct FormData {
6 name: String,
7 age: i8,
8 email: String,
9 password: String,
10}
11
12
13#[tuono_lib::api(POST)]
14async fn send_data(_req: Request) -> Redirect {
15 let data = _req.form_data::<FormData>().unwrap();
16 let mut message= "/?message=Hello ".to_string();
17 println!("Name: {}, Age: {}, Email: {}, Password. {}", data.name, data.age, data.email, data.password);
18 message.push_str(&data.name);
19 Redirect::to(&message.to_string())
20}First, we'll create the structure of our data, followed by a function to process it. The macro above it indicates that this is not a handler, but a POST-type API route. All functions must accept a Request, which contains methods like params, body, or form_data(). I also created a message variable to return status via search parameters.
In index.tsx, we'll also add a function to handle search parameters.
1const path = useRouter()
2 useEffect(()=> {
3 const msg = path.query?.message;
4 console.log(msg)
5 if( msg && typeof msg === "string"){
6 toast.success(decodeURIComponent(msg))
7 }
8 }, [path.query])In __layout.stx, let's also place the Toaster component.
1import type { JSX } from 'react'
2import { TuonoScripts } from 'tuono'
3import type { TuonoLayoutProps } from 'tuono'
4import { Toaster } from 'react-hot-toast'
5import '../styles/global.css'
6
7export default function RootLayout({
8 children,
9}: TuonoLayoutProps): JSX.Element {
10 return (
11 <html>
12 <head>
13 <meta name="viewport" content="width=device-width, initial-scale=1" />
14 </head>
15 <body>
16 <main>{children}</main>
17 <TuonoScripts />
18 <Toaster/>
19 </body>
20 </html>
21 )
22}
23And we are done! Check Tuono official websites.
![[object Object]](/_next/image?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fd9iesoru%2Fproduction%2Fc9145ebb157e3918776e11436cbbb7a932497f40-3024x1964.png&w=640&q=75)
