rspc Integration
Prisma Client Rust has first-class support forrspc (opens in a new tab),a server-agnostic router that can automatically generate TypeScript bindings.
The examples use the following schema:
modelPost { idString@id@default(cuid()) titleString comments Comment[]}modelComment { idString@id@default(cuid()) contentString post Post@relation(fields: [postID], references: [id]) postIDString}
Setup
When therspc
feature is enabled for bothprisma-client-rust
andprisma-client-rust-cli
,all data types generated by PCR will implementspecta::Type
,and aFrom<queries::Error>
implementation is generated forrspc::Error
.
Queries
The following is possible when combining PCR and rspc:
use rspc::Router;fnmain()->Router {Router::<prisma::PrismaClient>::new().query("posts",|db, _: ()|asyncmove {let posts= db.post().find_many(vec![]).exec().await?;Ok(posts) }).build()}
When TypeScript is generated it will look something like this:
exporttypeOperations= { queries: { key: ["posts"], result:Array<Post> }};exportinterfacePost { id:string, title:string }
This also works forselect & include:
use rspc::Router;fnmain()->Router {Router::<prisma::PrismaClient>::new().query("posts",|db, _: ()|asyncmove {let posts= db.post().find_many(vec![]).select(post::select!({ id title })).exec().await?;Ok(posts) }).build()}
The generated TypeScript will look something like this:
exporttypeOperations= { queries: { key: ["posts"], result:Array<{ id:string, title:string}> }};
As of 0.6.4, dedicated types will be generated for named include/selects.These are only provided for extenuating cirsumstances though,and it is instead recommended that you use helpers fromrspc
such asrspc.inferProcedureResult
to access procedure resultsinstead of relying on the named types.
Relation types
It is important to note that the types generated for models do not have fields for relations.This is done to provide a better experience when usinginclude
,at the expense of a worse experience usingwith
/fetch
for fetching relations.
This tradeoff was made as a result of our experience buildingSpacedrive (opens in a new tab).Instead of all our TypeScript functions having to verify at runtime whether a relation had been loaded or not,as is the case when usingwith/fetch
,each function has to explicitly specify the relations it wants loaded:
import { User, Post }from"./exported-types"// Only recieves scalar fields of UserfunctionneedsOneUser(user:User) {}// Receives scalar fields + posts relation of UserfunctionneedsOneUserWithPosts(user:User& { posts:Post[] }) {}
There are a few downsides to this approach:
- It is necessary to know the name and type of the relation you are including in TypeScript(though this is being worked on)
with/fetch
can be modified dynamicallywith/fetch
provides better editor autocomplete
However, we've found that these downsides are outweighed by the benefits of having full type-safety across your backend and frontend.
Error Handling
Sincerspc::Error
can implementFrom<queries::Error>
,all of Rust's regular error handling conventions are available.
The?
operator can be used to extract success values and return early if an error is encountered,automatically converting the Prisma error to an rspc error.This has the problem of needing to wrap the extracted value inOk
before return from a resolver.
Another approach is adding.map_err(Into::into)
before returning from a resolver.This causes the Prisma error to be converted to an rspc error while keeping the Result intact,as demonstrated below:
use rspc::Router;fnmain()->Router {Router::<prisma::PrismaClient>::new().query("posts",|db, _: ()|asyncmove { db.post().find_many(vec![]).exec().await.map_err(Into::into) }).build()}
For specifics on error handling in rpsc, seethe documentation (opens in a new tab).