rspc
rspc copied to clipboard
Website for exploring router schema. Similar to GraphiQL
This idea is cool! Essentially sort of like a generated docs page for our API.
I thought so too! I have always loved using GraphQL Playground to explore the data before the frontend has been developed. It is something I can do in rspc because I have all of the types at runtime on the Rust side as required for the rest of the system to work. Something like trpc can't do the same thing because the types do not exist at runtime (in Javascript) although I am sure if they wanted to they could find a way to store them.
I have been working on this feature locally and have made some progress but I am not ready to publish it yet. Hopefully, it won't be long until an MVP of the playground is released.
Was looking into spacedrive and found this repo. I'm in the middle of building an API for personal use, and decided to use Rust as I wanted to learn the language.
I have used TRPC before and it was great to see there's an alternative in Rust, too. As for now, I'm playing around with the axum_handler
. However, I'm wondering how does one pass a connection pool to the router, do we use the global context?
In axum we could use the Extension
helper to achieve this, is there any ways we could leverage the Extension
helper too in RSPC router?
let router = rspc::Router::<Ctx>::new()
.config(Config::new().export_ts_bindings("./generated/bindings.ts"))
.query("version", |_, _: ()| env!("CARGO_PKG_VERSION"))
.query("echo", |_, v: String| v)
.query("error", |_, _: ()| {
Err(rspc::Error::new(
rspc::ErrorCode::InternalServerError,
"Something went wrong".into(),
)) as Result<String, rspc::Error>
})
.query("transformMe", |_, _: ()| "Hello, world!".to_string())
.mutation("sendMsg", |_, v: String| {
println!("Client said '{}'", v);
v
})
.build()
.arced();
let cors = CorsLayer::new()
.allow_methods(Any)
.allow_headers(Any)
.allow_origin(Any);
let pool = MySqlPoolOptions::new()
.max_connections(5)
.connect(&env::var("DATABASE_URL")?)
.await
.expect("Failed to connect to database");
let app = axum::Router::new()
.route("/", get(|| async { "Hello 'rspc'!" }))
.route(
"/rspc/:id",
router.clone().axum_handler(|Path(path): Path<String>| {
println!("Client requested operation '{}'", path);
Ctx {}
}),
)
.route("/rspcws", router.axum_ws_handler(|| Ctx {}))
.route("/expenses", get(get_expenses))
.layer(cors)
.layer(Extension(pool));
This above is my code at the moment.
You can technically use an Axum extractor (Path
in that example could be an Extension
extractor) but I think I am going to remove that within the week as I see it as an anti-pattern, it's less typesafe, is Axum specific (I want to support more webservers) and is hard to maintain on my end.
The best solution is to capture the variables into the axum_handler
closure (ensure you have the move
keyword on the closure) like .axum_handler(move || Ctx { db })
. This will probably require your database to be Arc
ed although if you're using a connection pool I would be surprised if this hasn't already been done for you. You can actually do this same thing in Axum handlers but it is less convenient so a lot of people use extensions that trades the conventience for a runtime time error if the expected extension does not exist. This page on the docs site explains why I took this approach for rspc.
I will get onto documenting this better because it seems to be something that many people have struggled with.
I would give #24 a look as it is someone with the same issue and refer to this example I created for that issue.
In Spacedrive we do:
tauri::Builder::default()
.plugin(sdcore::rspc::integrations::tauri::plugin(router, {
let node = node.clone();
move || node.get_request_context()
}))
And then node.get_request_contex()
returns a struct containing the database (technically containing a thing to fetch the database because we use multiple but that's beside the point). The syntax shown in this example uses a scope that allows the node.clone();
to happen outside the closure as we use the node
variable further down in the main
function (where this snippit it from) and when you move a variable into a closure it can then only be used from within that closure unless you put it in an Arc
which allows multiple references hence we run node.clone()
for a new reference and move the reference into the closure.