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.

ORMRusttype-safeauto-generatedPrisma ecosystem

GitHub Overview

Brendonovich/prisma-client-rust

Type-safe database access for Rust

Stars1,926
Watchers15
Forks123
Created:May 25, 2021
Language:Rust
License:Apache License 2.0

Topics

prismaprisma-clientprisma-client-rustrust

Star History

Brendonovich/prisma-client-rust Star History
Data as of: 7/19/2025, 08:12 AM

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

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)
    }
}