Learn Tauri by Doing - Part 2: First method and APIs call
Tags: rust, tauri, javascript, tutorial
Published: 2024-05-08 07:59:42

### Introduction
Here we are at the second part of our journey in Tauri. Last time we left after finishing analyzing a project structure, explaining also how to invoke a Rust function from the JS frontend. If you've lost the previous part, you can find it at [this link](https://dev.to/giuliano1993/learn-tauri-by-doing-part-1-introduction-and-structure-1gde).
Today we're going to learn how to call some APIs from Rust and return the result to the JS frontend.
So, since the first time it was more theory than code; let's lose no more time and get started!

### The setup
There are two ways to proceed in setup part and, then, in the following ones. These two ways can be applied to what we'll see in this API part but also in the following ones.
In fact you can access most functionalities both from Rust and from JS, using Tauri provided APIs.
If you don't want to use Rust while developing in Tauri, you can easily rely on JS modules and go on with your work; that is an amazing feature that really helps anyone to get started with this tool.
So in this very first case, you can go in your `tauri.conf.json`
and add to your `allowlist`:

```json
//tauri.conf.json
...
"tauri":{
	"allowlist":{
		"http":{
			"scope":[] //an array of allowed url / url pattern that can be called from the web view,
			"all": true // this is used to allow all http features
		}
	}
}
```

Once you configured, you'll be able accessing all Tauri-provided HTTP APIs directly from your JS.
Since these articles will focus on Rust development, as previously said, i won't go deeper on this part.
You can find all the documentation about JS Tauri HTTP Api [here](https://tauri.app/v1/api/js/http)

So, if we mean to follow the Rust way we won't need to edit the `tauri.conf.json`; but we will need three packages instead: `serde`, `serde_json` and `reqwest`. While the first two are already installed when we init our Tauri project, we will need to add reqwest with it's json feature. So enter the src-tauri folder and run


```bash
cargo add reqwest --features=json
```

This will download the package and add it to your `Cargo.toml` file
We're going to use `serde` and `serde_json` packages to serialize and deserialize data, while `reqwest` will be used, as the name suggests, to create our requests.

### The first method

So the first thing i wanted to build Rust-side when started developing my application was a call to GPT to quickly translate my Italian newsletter to its English version, so that I only had to check on that translation and correct it, if necessary, instead of writing it all over again.

So the first step is to create a function that is callable from the front-end; as we saw last time, this can be accomplished through the `#[tauri::command]` attribute.

```rust
// main.rs

#[tauri::command]
async fn translate(text: String) -> String {
	
}
```

As you see, we're creating an async function, that receives a `String` as a parameter and returns a `String` ( GPT Answer ). Now first of all we need a body for our request. To be precise, we need a json body; to do that, we can use `serde_json::json` method.

```rust
//main.rs

use serde_json::json;

#[tauri::command]
async fn translate(text: String) -> String {
	let body = json!({
        "model":"gpt-3.5-turbo",
        "messages": [
          {
            "role": "system",
            "content": "Translate the text you receive from italian to english. Return only the translated text. Format it as unparsed markdown."
          },
          {
            "role":"user",
            "content": text
          }
        ]
   });
}

```

Once we have the body for our request it's time to create it. We also need a token for GPT requests ( and for most APIs actually ). For the moment we'll hard code it into our function, next time we'll talk about file system and how to handle this part more securely.


```rust

//main.rs
//... 
let open_ai_token = "this_is_my_token";

let response = Client::new()
.post("https://api.openai.com/v1/chat/completions")
.bearer_auth(open_ai_token)
.json(&body)
.send().await.unwrap()
.text().await.unwrap();

return response
```


Okay; now there is some stuff going on here so let's break it down.
First of all we define our token, fine.
After that we create a new `reqwest::Client` to make our request. We define it as a `POST` request thank to the `post` function and we specify out bearer token.
After that we add a `json` body to the request.
Once we do this, we send our request, wait for the result retrieve  it with `unwrap`. 

> If you're not familiar with Rust, in cases where a function returns Result that can be either a value or an error, unwrap directly catches the value, if there is one, otherwise it panics. It is best used when it's probably there will be no actual error in the execution, otherwise [**Pattern Matching**](https://doc.rust-lang.org/book/ch06-02-match.html) is a way better solution.

Once we have the result we want to transform it into a String we can return to our front-end, so once again, we use the `.await.unwrap()` structure.

Finally, we just return our response.

Apparently, the function is complete. In order to make this function available in the front-end we need to take one more step. In our `main` function, we need to add the `translate` function to the array of generated handlers; So let's add it like this

```rust
.invoke_handler(tauri::generate_handler![greet, translate])
```

and then we're ready to move to our frontend.

### The front-end

Now that we have our rust function ready, it's time to call it from the front-end. So let's make a new component. As I said, you can use whatever framework you prefer for this part; I'm using Vue.

```vue
<script setup lang="ts">
// Translate.vue
import { ref } from "vue";
import { invoke } from "@tauri-apps/api/tauri";

const text = ref("");
const translatedText = ref(""); 

async function translate() {
	  await invoke("translate",{text: text.value}).then((res:string)=>{
	  const parsed =  JSON.parse(res)
	  translatedText.value = parsed.choices[0].message.content
  })
}
</script>

  

<template>
  <label>Text to translate</label>
  <input type="text" v-model="text">
  <button @click="translate">Translate</button>
  <div>
    {{ translatedText }}
  </div>
</template>
```

Here's the component: as you can see it's nothing special: we just need to import the `invoke` function from Tauri and call it, passing our function name as first argument and an object with the arguments needed by the rust function as second one. Since we're returning a String we need to transform it into JSON before being able to get our result and showing our translation.

### Improve the code a little

As we saw, we are returning a string and transforming it to Json in the front end. We could probably do better!

Let's get back to our main.rs file for a small improvement.
Since we know the structure of the response we're receiving, we can prepare some `structs` to cope with it and return a parsed already object to the frontend.
So let's add these three structures at the top of our main.rs

```rust

#[derive(Serialize, Deserialize)]
pub struct OpenAiMessage {
    role: String,
    pub content: String
}

  

#[derive(Serialize,Deserialize)]
pub struct OpenAiChoice {
    pub message: OpenAiMessage,
}

  

#[derive(Serialize,Deserialize)]
pub struct OpenAiCompletion {
    pub choices: Vec<OpenAiChoice>
}

```

As you see, we're just working with one key from the actual response, which is `choices`. In more realistic cases you may want to add some more granular control but for the moment is fine like this.
As you can see we derive serde `Serialize` and `Deserialize` traits for all these structs. That's because, everything returned by a Tauri command must be Serializable, and so implement that trait; as for Deserialize, we need it to transform the response into our object.

Let's go  to our translate function:

```rust
//main.rs
//...
async fn translate(text: String) -> OpenAiCompletion {
//...
let response = Client::new()
.post("https://api.openai.com/v1/chat/completions")
.bearer_auth(open_ai_key)
.json(&body)
.send().await.unwrap();

   let open_ai_response: OpenAiCompletion = response.json().await.unwrap();

  

   return open_ai_response

```

As you can see the last part of the response chain was removed because we're not transforming it into a text anymore; instead we read the response as a json and assign that value to open_ai_response, infering it as a OpenAiCompletion. In order to return it we also changed the return type of the `translate` function.

Now we can get back to our frontend and update the function to receive this new value

```rust
// translate.vue
async function translate() {
  await invoke("translate",{text: text.value}).then((res:any)=>{
    translatedText.value = res.choices[0].message.content
  })
}

```


### Conclusion

This was our very first step getting our hands dirty with Tauri and Rust.
We could go further and deeper and we will, more and more; we will organize our commands in modules and will securely save our tokens; one step at a time we'll learn all we need to about this great tool! 
For the moment I hope you enjoy the article and hopefully learned something.
I'm looking forward writing the next part.
Until then, 
Happy Coding 0_1