How to Add a New Backend Command
Relevant source files
The following files were used as context for generating this wiki page:This document outlines the process for adding a new backend command to the Scripty application. The application utilizes the Tauri framework, which allows a Rust backend to expose functions that can be invoked from the JavaScript frontend. Adding a new command involves defining the function in Rust, registering it with the Tauri application builder, and then calling it from the React frontend.
The architecture separates concerns, with the Rust backend handling system-level operations like file I/O and process execution, while the React frontend manages the user interface and state. Commands are the bridge between these two layers.
Backend Implementation (Rust)
The backend logic is contained within the src-tauri directory. Adding a new command requires modifying the Rust code in this crate.
Step 1: Defining the Command Function
A backend command is a standard Rust function decorated with the #[tauri::command] attribute. This attribute makes the function callable from the frontend.
- Create a Rust function in
src-tauri/src/lib.rs. - Add the
#[tauri::command]attribute directly above the function signature. - Define arguments and a return type. Arguments and return values must be serializable. The
serdecrate, listed as a dependency, handles this serialization. For functions that can fail, it is best practice to return aResult<T, E>, whereEis aStringto provide a descriptive error message to the frontend.
Sources: src-tauri/src/lib.rs:27, src-tauri/Cargo.toml:19-20
Below is an example of the read_ps1 command, which takes a file path as a string and returns either a tuple (String, String) on success or a String error message on failure.
// src-tauri/src/lib.rs:55-61
#[tauri::command]
fn read_ps1(path: String) -> Result<(String, String), String> {
match fs::read_to_string(&path) {
Ok(content) => Ok((path, content)),
Err(_e) => Err(format!("Failed to resolve the .ps1 file")),
}
}
Sources: src-tauri/src/lib.rs:55-61
Step 2: Registering the Command
After a command function is defined, it must be registered with the Tauri application instance so the frontend can find and call it. This is done in the run() function within src-tauri/src/lib.rs.
The invoke_handler method on the tauri::Builder is used for this purpose. The tauri::generate_handler![] macro takes a list of command function names and prepares them for the handler.
- Open
src-tauri/src/lib.rs. - Locate the
run()function. - Add the name of your new command function to the list inside the
tauri::generate_handler![]macro.
// src-tauri/src/lib.rs:64-74
pub fn run() {
tauri::Builder::default()
// ... other plugins
.invoke_handler(tauri::generate_handler![
open_terminal,
read_ps1,
run_sys_terminal,
open_admin_terminal
// <-- Add your new command here
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Sources: src-tauri/src/lib.rs:64-74, src-tauri/src/main.rs:5
The following diagram illustrates the backend process from definition to registration.
Sources: src-tauri/src/lib.rs
Frontend Integration (JavaScript/React)
Once the backend command is defined and registered, it can be called from any React component in the src directory.
Step 3: Invoking the Command
The @tauri-apps/api/core package provides the invoke function, which is used to call backend commands.
- Import
invokefrom@tauri-apps/api/core. - Call
invokewith the command name and arguments.- The first argument is the name of the Rust function in
snake_case. - The second argument is an object where keys are the argument names from the Rust function's signature. If the function has no arguments, this can be omitted.
- The first argument is the name of the Rust function in
- Handle the response.
invokereturns a Promise that resolves with the success value (Okvariant) or rejects with the error value (Errvariant) from the Rust function.
The following example from Form.jsx shows how the read_ps1 command is invoked.
// src/components/Form/Form.jsx:19-25
try {
const [filename, content] = await invoke("read_ps1", { path: selected });
file = filename;
end = content;
} catch (err) {
// ... error handling
}
Sources: src/components/Form/Form.jsx:19-25
This sequence diagram shows the complete flow from a user action in the UI to the execution of a backend command and the return of data.
Existing Backend Commands
The following table summarizes the commands currently implemented in the backend.
| Command Name | Description | Parameters | Source |
|---|---|---|---|
open_terminal | Spawns a new PowerShell terminal with the given content as an argument. | content: String | src-tauri/src/lib.rs:27-30 |
open_admin_terminal | Spawns a new PowerShell terminal with administrator privileges. | content: String | src-tauri/src/lib.rs:33-44 |
run_sys_terminal | Executes a command as the SYSTEM user using an embedded syspwsh.exe. | content: Option<String> | src-tauri/src/lib.rs:47-52 |
read_ps1 | Reads the content of a .ps1 file from a given path. | path: String | src-tauri/src/lib.rs:55-61 |
Sources: src-tauri/src/lib.rs:27-61
Conclusion
Adding a new backend command in Scripty is a straightforward, three-step process:
- Define a Rust function with the
#[tauri::command]attribute insrc-tauri/src/lib.rs. - Register the function name in the
invoke_handlerwithin therun()function. - Invoke the command from the React frontend using the
invokefunction from the Tauri API, passing the command name and any required arguments.
This clear separation between the backend and frontend allows for robust, system-level capabilities to be securely exposed to the user interface.