Crate tauri_plugin_graphql
source ·Expand description
This crate contains a Tauri plugin used to expose a async_graphql
GraphQL endpoint through Tauri’s IPC system. This plugin can be used as
safer alternative to Tauri’s existing Command API since both the Rust and
JavaScript side of the interface can be generated from a common schema.
Rationale
Especially in bigger projects that have specialized teams for the Frontend
and Rust core the existing command API falls short of being an optimal
solution. The Frontend is tightly coupled through invoke()
calls to
backend commands, but there is no type-safety to alert Frontend developers
to changes in command signatures. This results in a very brittle interface
where changes on the Rust side will inadvertently break code in the
Frontend. This problem is similar exiting REST APIs, where the absence of a
formal contract between the server and the frontend makes future changes
very difficult.
We can employ the same techniques used in traditional web development and use shared schema that governs which types, methods, etc. are available. GraphQL is such a schema language.
Examples
For the following examples, it is assumed you are familiar with Tauri Commands
, Events
and GraphQL
.
Queries
An example app that implements a very simple read-only todo-app using GraphQL:
use async_graphql::{Schema, EmptySubscription, EmptyMutation, Object, SimpleObject, Result as GraphQLResult};
#[derive(SimpleObject, Debug, Clone)]
struct ListItem {
id: i32,
text: String
}
impl ListItem {
pub fn new(text: String) -> Self {
Self {
id: rand::random::<i32>(),
text
}
}
}
struct Query;
#[Object]
impl Query {
async fn list(&self) -> GraphQLResult<Vec<ListItem>> {
let item = vec![
ListItem::new("foo".to_string()),
ListItem::new("bar".to_string())
];
Ok(item)
}
}
let schema = Schema::new(
Query,
EmptyMutation,
EmptySubscription,
);
tauri::Builder::default()
.plugin(tauri_plugin_graphql::init(schema));
Mutations
GraphQL mutations provide a way to update or create state in the Core.
Similarly to queries, mutations have access to a context object and can manipulate windows, menus or global state.
use async_graphql::{Schema, Object, Context, EmptySubscription, EmptyMutation, SimpleObject, Result as GraphQLObject};
use tauri::{AppHandle, Manager};
use std::sync::Mutex;
#[derive(Debug, Default)]
struct List(Mutex<Vec<ListItem>>);
#[derive(SimpleObject, Debug, Clone)]
struct ListItem {
id: i32,
text: String
}
impl ListItem {
pub fn new(text: String) -> Self {
Self {
id: rand::random::<i32>(),
text
}
}
}
struct Query;
#[Object]
impl Query {
async fn list(&self, ctx: &Context<'_>) -> GraphQLObject<Vec<ListItem>> {
let app = ctx.data::<AppHandle>().unwrap();
let list = app.state::<List>();
let list = list.0.lock().unwrap();
let items = list.iter().cloned().collect::<Vec<_>>();
Ok(items)
}
}
struct Mutation;
#[Object]
impl Mutation {
async fn add_entry(&self, ctx: &Context<'_>, text: String) -> GraphQLObject<ListItem> {
let app = ctx.data::<AppHandle>().unwrap();
let list = app.state::<List>();
let mut list = list.0.lock().unwrap();
let item = ListItem::new(text);
list.push(item.clone());
Ok(item)
}
}
let schema = Schema::new(
Query,
Mutation,
EmptySubscription,
);
tauri::Builder::default()
.plugin(tauri_plugin_graphql::init(schema))
.setup(|app| {
app.manage(List::default());
Ok(())
});
Subscriptions
GraphQL subscriptions are a way to push real-time data to the Frontend. Similarly to queries, a client can request a set of fields, but instead of immediately returning a single answer, a new result is sent to the Frontend every time the Core sends one.
Subscription resolvers should be async and must return a Stream
.
use async_graphql::{
futures_util::{self, stream::Stream},
Schema, Object, Subscription, EmptySubscription,
EmptyMutation, SimpleObject, Result as GraphQLResult
};
struct Query;
#[Object]
impl Query {
async fn hello_world(&self) -> GraphQLResult<&str> {
Ok("Hello World!")
}
}
struct Subscription;
#[Subscription]
impl Subscription {
async fn hello_world(&self) -> impl Stream<Item = &str> {
futures_util::stream::iter(vec!["Hello", "World!"])
}
}
let schema = Schema::new(
Query,
EmptyMutation,
Subscription,
);
tauri::Builder::default()
.plugin(tauri_plugin_graphql::init(schema));
Stability
To work around limitations with the current command system, this plugin
directly implements an invoke handler instead of reyling on the
[tauri::generate_handler
] macro.
Since the invoke handler implementation is not considered stable and might
change between releases this plugin has no backwards compatibility
guarantees.
Re-exports
pub use async_graphql;