以前、prisma-clientでGraphQLに入門しました。
せっかくRustを触りだしたので、prismaでRustクライアントを出力できるprisma-client-rustを使用してRESTアプリケーションを構築してみたいと思います。
GitHub - Brendonovich/prisma-client-rust: Type-safe database access for Rust
Cargoパッケージの初期化
まずは元になるパッケージを作成します。
% cargo new prisma-for-rest
Created binary (application) `prisma-for-rest` package
せっかくこの前学んだので、このパッケージに移動して安定版の最新バージョンを参照するrust-toolchain.toml
を作っておきます。
[toolchain]
channel = "stable-2023-07-12"
続けてデータベース用のパッケージを作成します。
cargo new database
データベース用のパッケージに移動し、prisma-client-rustを依存関係に追加します。
[dependencies]
prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust", tag = "0.6.8" }
prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client-rust", tag = "0.6.8" }
database/src/main.rs
に、prisma-client-rust用のCLIを操作するための実装を追記します。
fn main() {
prisma_client_rust_cli::run();
}
prisma-client-rust-cliの呼び出しを簡素化するため、.cargo/config.toml
というファイルを作成して以下のような設定を追記します。
[alias]
prisma = "run --"
これにより、cargo prisma
でprisma-client-rust-cliの呼び出しが可能になりました。
% cargo prisma --help
Finished dev [unoptimized + debuginfo] target(s) in 0.49s
Running `target/debug/database --help`
◭ Prisma is a modern DB toolkit to query, migrate and model your database (https://prisma.io)
Usage
$ prisma [command]
Commands
init Set up Prisma for your app
generate Generate artifacts (e.g. Prisma Client)
db Manage your database schema and lifecycle
migrate Migrate your database
studio Browse your data with Prisma Studio
validate Validate your Prisma schema
format Format your Prisma schema
Flags
--preview-feature Run Preview Prisma commands
Examples
Set up a new Prisma project
$ prisma init
Generate artifacts (e.g. Prisma Client)
$ prisma generate
Browse your data
$ prisma studio
Create migrations from your Prisma schema, apply them to the database, generate artifacts (e.g. Prisma Client)
$ prisma migrate dev
Pull the schema from an existing database, updating the Prisma schema
$ prisma db pull
Push the Prisma schema state to the database
$ prisma db push
Validate your Prisma schema
$ prisma validate
Format your Prisma schema
$ prisma format
prismaスキーマの初期化
Prisma CLIが使えるようになったので、CLIからPrismaの初期化を行います。
データベースはガッツリしたのを使いたくないので、SQLiteを使用しました。
% cargo prisma init --datasource-provider sqlite
Finished dev [unoptimized + debuginfo] target(s) in 0.41s
Running `target/debug/database init --datasource-provider sqlite`
✔ Your Prisma schema was created at prisma/schema.prisma
You can now open it in your favorite editor.
warn You already have a .gitignore file. Don't forget to add `.env` in it to not commit any private information.
Next steps:
1. Set the DATABASE_URL in the .env file to point to your existing database. If your database has no tables yet, read https://pris.ly/d/getting-started
2. Run prisma db pull to turn your database schema into a Prisma schema.
3. Run prisma generate to generate the Prisma Client. You can then start querying your database.
More information in our documentation:
https://pris.ly/d/getting-started
初期化されると、prisma/schema.prisma
と.env
が出来上がっています。
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema
# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings
DATABASE_URL="file:./dev.db"
スキーマには、prismaのチュートリアルでおなじみのユーザーとポストのモデルを使っていきます。
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
}
この内容で一旦マイグレーションします。
%cargo prisma migrate dev
Finished dev [unoptimized + debuginfo] target(s) in 1.06s
Running `target/debug/database migrate dev`
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": SQLite database "dev.db" at "file:./dev.db"
SQLite database dev.db created at file:./dev.db
✔ Enter a name for the new migration: … init
Applying migration `20230805123330_init`
The following migration(s) have been created and applied from new schema changes:
migrations/
└─ 20230805123330_init/
└─ migration.sql
Your database is now in sync with your schema.
Running generate... (Use --skip-generate to skip the generators)
✔ Created ./package.json
added 2 packages, and audited 3 packages in 9s
found 0 vulnerabilities
added 2 packages, and audited 5 packages in 7s
found 0 vulnerabilities
✔ Installed the @prisma/client and prisma packages in your project
Error: Generator at /home/username/Project/study/prisma-for-rest/database/node_modules/@prisma/client/generator-build/index.js could not start:
マイグレーションはできましたが、prisma generate
時になんかエラーが出ているようです。
node_modulesのインストールが行われているので、jsのPrismaクライアントが作成されてしまっているようです。
スキーマのgeneratorセクションを修正します。
generator client {
provider = "cargo prisma"
output = "../src/prisma.rs"
}
再度prisma generate
を実行します。
% cargo prisma generate
Finished dev [unoptimized + debuginfo] target(s) in 1.24s
Running `target/debug/database generate`
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
✔ Generated Prisma Client Rust to ./prisma/src/prisma.rs in 643ms
成功しましたね。
PrismaのRustクライアントでデータ操作する
先程のprisma generate
で、database/src/prisma.rs
が作成されているはずです。
これを使用することで、SQLiteのデータベース操作を行うことができるようになります。
とりあえず、prisma.rs
を使用するにあたって必要な依存ライブラリをdatabase
に追加します。
[dependencies]
# ...中略...
random-string = "1.0.0" # ランダムな値のレコード生成に使用します
serde = "1.0.181" # prisma.rsで使用するライブラリです
tokio = "1.29.1" # prisma-clientの実行に利用します
prisma.rs
のクライアントを実行するために、lib.rs
にprismaモジュールを定義します。
#[allow(warnings, unused)]
pub mod prisma;
更にdatabase/src/bin/prisma_controller.rs
を作成し、prismaモジュールから操作を実行してみましょう。
use random_string::generate;
use database::prisma::PrismaClient;
#[tokio::main]
async fn main() {
let client: PrismaClient = PrismaClient::_builder().build().await.unwrap();
// メールアドレスの作成(ランダム文字列を使用)
let email = format!("{}@test.com", generate(10, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")).to_string();
// ユーザーの作成
let user = client.user().create(email.clone(), vec![]).exec().await.unwrap();
// 先程作成したユーザーに紐づく投稿を作成
client
.post()
.create(
"what up".to_string(),
database::prisma::user::UniqueWhereParam::IdEquals(user.id.clone()),
vec![],
)
.exec()
.await.unwrap();
}
上記のように実装しました。
これを実行してみましょう。
%cargo run --bin prisma_controller
Compiling fastrand v1.9.0
Compiling random-string v1.0.0
Compiling database v0.1.0 (/home/username/Project/study/prisma-for-rest/database)
Finished dev [unoptimized + debuginfo] target(s) in 30.09s
Running `/home/username/Project/study/prisma-for-rest/target/debug/prisma_controller`
実行できたようです。
sqliteで確認してみます。
% sqlite3 prisma/dev.db
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
sqlite> select * from User join Post on User.id = Post.authorId;
1|Yzru16Cv7c@test.com||1|what up||0|1
作成されていますね!
しっかり生成したprismaクライアントが使えているようです。
actix-webから操作してみる
では本題のRESTアプリケーションを実装してみましょう。
といっても、サンプル実装があるのでこれをベースに実装します。
prisma-client-rust/examples/actix/src/main.rs at main · Brendonovich/prisma-client-rust · GitHub
prisma-for-rest
に戻って、新たにREST API用のパッケージを作成します。
cargo new rest-api
ワークスペースにrest-apiパッケージを追加します。
[workspace]
members = [
"database",
"rest-api", # これを追加
]
rest-api
パッケージに移動して、prismaクライアントとactixの依存関係を追加していきます。
[dependencies]
actix-web = "4.3.1"
database = { path = "../database" }
とりあえずactixの起動とユーザーの取得ができるようにmain.rs
を実装します。
use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};
use database::prisma::PrismaClient;
#[get("/users")]
async fn get_users(client: web::Data<PrismaClient>) -> impl Responder {
let users = client.user().find_many(vec![]).exec().await.unwrap();
HttpResponse::Ok().json(users)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let client = PrismaClient::_builder().build().await.unwrap();
let data = web::Data::new(client);
HttpServer::new(move || {
App::new()
.app_data(data.clone())
.service(get_users)
})
.bind(("127.0.0.1", 3001))?
.run()
.await
}
実行すると以下のようなエラーが出ました。
thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', /home/username/.cargo/git/checkouts/prisma-client-rust-fa967aa5ad0ec391/51f0473/src/client.rs:167:18
一旦実装をコメントアウトしてtokioを依存関係に追加し、先ほどdatabase
パッケージで実装したクライアント実装を持ってきて再実行してみました。
use database::prisma::PrismaClient;
#[tokio::main]
async fn main() {
let client: PrismaClient = PrismaClient::_builder().build().await.unwrap();
let email = "test@test.com".to_string();
// ユーザーの作成
let user = client.user().create(email.clone(), vec![]).exec().await.unwrap();
// 先程作成したユーザーに紐づく投稿を作成
client
.post()
.create(
"what up".to_string(),
database::prisma::user::UniqueWhereParam::IdEquals(user.id.clone()),
vec![],
)
.exec()
.await.unwrap();
}
しかし同じエラーになってしまう。。
どうやら、実行ディレクトリ内に.env
がないと実行できないようです。
rest-api/.env
にデータベースのURLを設定しておきます。
DATABASE_URL="file:../database/prisma/dev.db"
この状態でactixの実装に戻して、再度実行してみます。
% cargo run
Compiling rest-api v0.1.0 (/home/username/Project/study/prisma-for-rest/rest-api)
Finished dev [unoptimized + debuginfo] target(s) in 27.17s
Running `/home/username/Project/study/prisma-for-rest/target/debug/rest-api`
起動できました。
curlでユーザー一覧を取得してみます。
% curl http://localhost:3001/users
[{"id":1,"email":"Yzru16Cv7c@test.com","name":null,"posts":null},{"id":2,"email":"test@test.com","name":null,"posts":null}]
取得できていますね!
補足:prisma.rsはコードリポジトリ上で管理すべきではない
The generated client must not be checked into source control. It cannot be transferred between devices or operating systems. You will need to re-generate it wherever you build your project. If using git, add it to your .gitignore file.
和訳:生成されたクライアントはソース管理にチェックインしないでください。デバイス間やオペレーティング システム間で転送することはできません。プロジェクトを構築する場合はどこでも再生成する必要があります。git を使用している場合は、それを .gitignore ファイルに追加します。
なので、今回Prismaが生成したdatabase/src/prisma.rs
は、毎回cargo prisma generate
して作成してあげる必要があるファイルとのことです。
プロジェクトで使用する場合はかならず.gitignore
に追加してあげるようにしましょう。
まとめ
とりあえずactixとprisma-client-rustで実装ができました。
ただ、データベースURLの管理についてはdotenv頼りではなくちゃんと管理してあげる必要がありますね。
ながくなってきたのでこのへんで終わりにします。