Prisma Client Rust
Prisma Client Rust is "a type-safe, auto-generated query builder for Rust" developed as an ORM (Object-Relational Mapping) crate to leverage the Prisma ecosystem in Rust. It provides a consistent and understandable API on top of Prisma's powerful database technology, functioning as an alternative to existing ORMs and tools like Diesel, SeaORM, and SQLx. In Rust applications, it enables consistent type-safe database access from frontend (Tauri, etc.) to server-side while leveraging the entire Prisma ecosystem.
GitHub Overview
Brendonovich/prisma-client-rust
Type-safe database access for Rust
Topics
Star History
Library
Prisma Client Rust
Overview
Prisma Client Rust is "a type-safe, auto-generated query builder for Rust" developed as an ORM (Object-Relational Mapping) crate to leverage the Prisma ecosystem in Rust. It provides a consistent and understandable API on top of Prisma's powerful database technology, functioning as an alternative to existing ORMs and tools like Diesel, SeaORM, and SQLx. In Rust applications, it enables consistent type-safe database access from frontend (Tauri, etc.) to server-side while leveraging the entire Prisma ecosystem.
Details
Prisma Client Rust 2025 edition is an unofficial product maintained by Brendonovich, yet is actively developed with generous support from Prisma's FOSS Fund. For developers looking to step away from NodeJS and create faster, more efficient applications, it harnesses existing tooling and terminology that Prisma Client JS users will be familiar with, making developing applications in Rust more approachable. For perhaps the first time, using Prisma in a frontend application is easy - using Prisma Client Rust in a desktop application powered by Tauri allows the entire Prisma ecosystem to be used while developing desktop applications, providing new possibilities.
Key Features
- Complete Type Safety: Compile-time safety leveraging Rust's type system
- Auto-generated Query Builder: Type-safe API automatically generated from Prisma schema
- Prisma Ecosystem Integration: Compatibility with existing Prisma toolchain
- SQLx Integration: Provides pure Rust stack (SeaORM + SQLx)
- Async Support: Full support for asynchronous Rust programming
- Desktop App Support: Optimized for desktop app development using Tauri
Pros and Cons
Pros
- Can leverage mature tools and terminology from the Prisma ecosystem in Rust
- Existing Prisma skills can be utilized when migrating from NodeJS
- More familiar API design for Prisma users compared to Diesel or SeaORM
- Easy utilization in desktop applications using Tauri
- Consistent development experience through pure Rust stack provision
- Inherits powerful Prisma migration system and development tools
Cons
- No official support as an unofficial product, relying on community support
- Requires Rust v1.62.0 or higher, necessitating relatively new Rust versions
- Requires special configuration, different setup from typical Rust projects
- Need to separately create prisma-client-rust-cli, adding complex additional setup
- Lower maturity compared to Diesel, with limited ecosystem
- Potential performance inferiority to Diesel
Reference Pages
- Prisma Client Rust Official Site
- Prisma Client Rust GitHub Repository
- Prisma Client Rust Installation Guide
Code Examples
Setup
# Cargo.toml
[dependencies]
prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust", tag = "0.6.11" }
prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust", tag = "0.6.11" }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
serde = { version = "1.0", features = ["derive"] }
[build-dependencies]
prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust", tag = "0.6.11" }
// schema.prisma
generator client {
provider = "cargo prisma"
output = "../src/prisma.rs"
}
generator db {
provider = "prisma-client-rust"
output = "../src/prisma.rs"
}
datasource db {
provider = "sqlite"
url = "file:./dev.db"
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int
author User @relation(fields: [authorId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Basic Usage
// src/main.rs
use prisma_client_rust::*;
#[cfg(feature = "sqlite")]
#[path = "prisma.rs"]
mod prisma;
use prisma::*;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = PrismaClient::_builder().build().await?;
// Database connection
client._db_push().await?;
println!("Database connected successfully!");
// Basic CRUD operations demo
basic_crud_example(&client).await?;
Ok(())
}
async fn basic_crud_example(client: &PrismaClient) -> Result<(), prisma_client_rust::QueryError> {
// User creation
let user = client
.user()
.create(
"[email protected]".to_string(),
vec![
user::name::set(Some("Alice Smith".to_string())),
],
)
.exec()
.await?;
println!("Created user: {:?}", user);
// User search
let users = client
.user()
.find_many(vec![])
.exec()
.await?;
println!("All users: {:?}", users);
// Get specific user
let alice = client
.user()
.find_unique(user::email::equals("[email protected]".to_string()))
.exec()
.await?
.unwrap();
println!("Found Alice: {:?}", alice);
// User update
let updated_user = client
.user()
.update(
user::email::equals("[email protected]".to_string()),
vec![
user::name::set(Some("Alice Johnson".to_string())),
],
)
.exec()
.await?;
println!("Updated user: {:?}", updated_user);
// User deletion
let deleted_user = client
.user()
.delete(user::email::equals("[email protected]".to_string()))
.exec()
.await?;
println!("Deleted user: {:?}", deleted_user);
Ok(())
}
Query Execution
// Advanced query examples
use prisma_client_rust::*;
use crate::prisma::*;
pub struct UserService {
client: PrismaClient,
}
impl UserService {
pub fn new(client: PrismaClient) -> Self {
Self { client }
}
// Multiple user creation
pub async fn create_users(&self, users_data: Vec<(String, Option<String>)>) -> Result<Vec<user::Data>, QueryError> {
let mut users = Vec::new();
for (email, name) in users_data {
let user = self.client
.user()
.create(
email,
vec![
user::name::set(name),
],
)
.exec()
.await?;
users.push(user);
}
Ok(users)
}
// Conditional search
pub async fn search_users(&self, name_filter: Option<String>) -> Result<Vec<user::Data>, QueryError> {
let mut where_clauses = vec![];
if let Some(name) = name_filter {
where_clauses.push(user::name::contains(name));
}
self.client
.user()
.find_many(where_clauses)
.order_by(user::created_at::order(SortOrder::Desc))
.exec()
.await
}
// Pagination
pub async fn get_users_paginated(
&self,
page: i32,
page_size: i32
) -> Result<Vec<user::Data>, QueryError> {
let skip = (page - 1) * page_size;
self.client
.user()
.find_many(vec![])
.skip(skip)
.take(page_size)
.order_by(user::created_at::order(SortOrder::Desc))
.exec()
.await
}
// Data retrieval with relations
pub async fn get_users_with_posts(&self) -> Result<Vec<UserWithPosts>, QueryError> {
let users = self.client
.user()
.find_many(vec![])
.include(user::include!{
posts
})
.exec()
.await?;
Ok(users.into_iter().map(|user| UserWithPosts {
id: user.id,
email: user.email,
name: user.name,
created_at: user.created_at,
posts: user.posts.unwrap_or_default(),
}).collect())
}
// Aggregation queries
pub async fn get_user_statistics(&self) -> Result<UserStatistics, QueryError> {
let total_users = self.client
.user()
.count(vec![])
.exec()
.await?;
let users_with_posts = self.client
.user()
.count(vec![
user::posts::some(vec![])
])
.exec()
.await?;
Ok(UserStatistics {
total_users,
users_with_posts,
users_without_posts: total_users - users_with_posts,
})
}
}
// Custom type definitions
#[derive(Debug, Clone)]
pub struct UserWithPosts {
pub id: i32,
pub email: String,
pub name: Option<String>,
pub created_at: DateTime<FixedOffset>,
pub posts: Vec<post::Data>,
}
#[derive(Debug, Clone)]
pub struct UserStatistics {
pub total_users: i64,
pub users_with_posts: i64,
pub users_without_posts: i64,
}
Data Operations
// Post management service
pub struct PostService {
client: PrismaClient,
}
impl PostService {
pub fn new(client: PrismaClient) -> Self {
Self { client }
}
// Post creation with user verification
pub async fn create_post(
&self,
author_email: String,
title: String,
content: Option<String>,
) -> Result<post::Data, Box<dyn std::error::Error>> {
// User existence check
let author = self.client
.user()
.find_unique(user::email::equals(author_email.clone()))
.exec()
.await?
.ok_or(format!("User with email {} not found", author_email))?;
// Post creation
let post = self.client
.post()
.create(
title,
user::email::equals(author_email),
vec![
post::content::set(content),
],
)
.exec()
.await?;
Ok(post)
}
// Get published posts
pub async fn get_published_posts(&self) -> Result<Vec<PostWithAuthor>, QueryError> {
let posts = self.client
.post()
.find_many(vec![
post::published::equals(true)
])
.include(post::include!{
author
})
.order_by(post::created_at::order(SortOrder::Desc))
.exec()
.await?;
Ok(posts.into_iter().map(|post| PostWithAuthor {
id: post.id,
title: post.title,
content: post.content,
published: post.published,
created_at: post.created_at,
author: post.author.unwrap(),
}).collect())
}
// Publish post
pub async fn publish_post(&self, post_id: i32) -> Result<post::Data, QueryError> {
self.client
.post()
.update(
post::id::equals(post_id),
vec![
post::published::set(true),
],
)
.exec()
.await
}
// Search posts
pub async fn search_posts(&self, query: String) -> Result<Vec<post::Data>, QueryError> {
self.client
.post()
.find_many(vec![
post::or(vec![
post::title::contains(query.clone()),
post::content::contains(query),
])
])
.order_by(post::created_at::order(SortOrder::Desc))
.exec()
.await
}
// Bulk deletion
pub async fn delete_unpublished_posts(&self) -> Result<i64, QueryError> {
self.client
.post()
.delete_many(vec![
post::published::equals(false)
])
.exec()
.await
}
}
#[derive(Debug, Clone)]
pub struct PostWithAuthor {
pub id: i32,
pub title: String,
pub content: Option<String>,
pub published: bool,
pub created_at: DateTime<FixedOffset>,
pub author: user::Data,
}
Configuration and Customization
// Application configuration
use prisma_client_rust::*;
use std::env;
pub struct DatabaseConfig {
client: PrismaClient,
}
impl DatabaseConfig {
// Database initialization
pub async fn new() -> Result<Self, Box<dyn std::error::Error>> {
let database_url = env::var("DATABASE_URL")
.unwrap_or_else(|_| "file:./dev.db".to_string());
let client = PrismaClient::_builder()
.with_url(database_url)
.build()
.await?;
// Database connection test
client._db_push().await?;
println!("Database connected successfully");
Ok(Self { client })
}
pub fn client(&self) -> &PrismaClient {
&self.client
}
// Database initial data
pub async fn seed_database(&self) -> Result<(), QueryError> {
println!("Seeding database...");
// Create sample users
let users = vec![
("[email protected]".to_string(), Some("Admin User".to_string())),
("[email protected]".to_string(), Some("Alice Smith".to_string())),
("[email protected]".to_string(), Some("Bob Johnson".to_string())),
];
for (email, name) in users {
let existing_user = self.client
.user()
.find_unique(user::email::equals(email.clone()))
.exec()
.await?;
if existing_user.is_none() {
self.client
.user()
.create(
email.clone(),
vec![
user::name::set(name),
],
)
.exec()
.await?;
println!("Created user: {}", email);
}
}
println!("Database seeding completed");
Ok(())
}
// Database cleanup
pub async fn cleanup_database(&self) -> Result<(), QueryError> {
println!("Cleaning up database...");
// Delete all posts
let deleted_posts = self.client
.post()
.delete_many(vec![])
.exec()
.await?;
println!("Deleted {} posts", deleted_posts);
// Delete all users
let deleted_users = self.client
.user()
.delete_many(vec![])
.exec()
.await?;
println!("Deleted {} users", deleted_users);
Ok(())
}
}
// Application main structure
pub struct App {
db: DatabaseConfig,
user_service: UserService,
post_service: PostService,
}
impl App {
pub async fn new() -> Result<Self, Box<dyn std::error::Error>> {
let db = DatabaseConfig::new().await?;
let user_service = UserService::new(db.client().clone());
let post_service = PostService::new(db.client().clone());
Ok(Self {
db,
user_service,
post_service,
})
}
pub async fn initialize(&self) -> Result<(), Box<dyn std::error::Error>> {
self.db.seed_database().await?;
Ok(())
}
pub fn user_service(&self) -> &UserService {
&self.user_service
}
pub fn post_service(&self) -> &PostService {
&self.post_service
}
}
Error Handling
// Custom error types
use thiserror::Error;
use prisma_client_rust::QueryError;
#[derive(Error, Debug)]
pub enum AppError {
#[error("Database error: {0}")]
Database(#[from] QueryError),
#[error("User not found: {email}")]
UserNotFound { email: String },
#[error("Post not found: {id}")]
PostNotFound { id: i32 },
#[error("Validation error: {message}")]
Validation { message: String },
#[error("Authorization error: {message}")]
Authorization { message: String },
}
pub type AppResult<T> = Result<T, AppError>;
// Service with error handling
pub struct SafeUserService {
client: PrismaClient,
}
impl SafeUserService {
pub fn new(client: PrismaClient) -> Self {
Self { client }
}
pub async fn create_user_safe(
&self,
email: String,
name: Option<String>,
) -> AppResult<user::Data> {
// Validation
if email.is_empty() || !email.contains('@') {
return Err(AppError::Validation {
message: "Invalid email format".to_string(),
});
}
// Duplicate check
let existing_user = self.client
.user()
.find_unique(user::email::equals(email.clone()))
.exec()
.await?;
if existing_user.is_some() {
return Err(AppError::Validation {
message: format!("User with email {} already exists", email),
});
}
// User creation
let user = self.client
.user()
.create(
email,
vec![
user::name::set(name),
],
)
.exec()
.await?;
Ok(user)
}
pub async fn get_user_by_email_safe(&self, email: String) -> AppResult<user::Data> {
let user = self.client
.user()
.find_unique(user::email::equals(email.clone()))
.exec()
.await?
.ok_or(AppError::UserNotFound { email })?;
Ok(user)
}
pub async fn update_user_safe(
&self,
email: String,
name: Option<String>,
) -> AppResult<user::Data> {
// User existence check
self.get_user_by_email_safe(email.clone()).await?;
// Execute update
let updated_user = self.client
.user()
.update(
user::email::equals(email),
vec![
user::name::set(name),
],
)
.exec()
.await?;
Ok(updated_user)
}
pub async fn delete_user_safe(&self, email: String) -> AppResult<user::Data> {
// User existence check
self.get_user_by_email_safe(email.clone()).await?;
// Check related posts
let post_count = self.client
.post()
.count(vec![
post::author::is(vec![
user::email::equals(email.clone())
])
])
.exec()
.await?;
if post_count > 0 {
return Err(AppError::Validation {
message: format!("Cannot delete user with {} posts. Delete posts first.", post_count),
});
}
// Execute deletion
let deleted_user = self.client
.user()
.delete(user::email::equals(email))
.exec()
.await?;
Ok(deleted_user)
}
}