Rust Diesel を使って PostgresQL にアクセスする
はじめに
今回は Diesel という Rust の ORM ライブラリを触ってみたいと思います。
最終的に、Rocket という web フレームワークに Http リクエストをして、Diesel からアクセスするというところまで行きたいと思います。
🔻Diesel
🔻Rocket
初期構築
プロジェクトの作成
任意の Rust プロジェクトを作成しておきます。
改めてになりますが、今回は既存のプロジェクトを使用します。
以下の記事にて、Rust の Rocket
を使ったプロジェクトを用意する手順がありますので、参考までに。
(短時間で構築できる内容となっていますので、ぜひ。)
Cargo.toml の更新
Cargo.toml
に以下のクレートを追加します。
diesel = { version = "2.1.0", features = ["postgres"] }
dotenvy = "0.15"
Diesel cli のインストール
次に Diesel
の cli をインストールしておきます。
以下のコマンドをターミナル等で実行します。
(既に cli がインストールされている場合は、スキップで問題ございません。)
$ cargo install diesel_cli
DB の作成
プロジェクト直下に、.env
ファイルを作成し、以下のようにデータベースのURLを環境変数に定義します。
username
と password
の部分はご自身のローカル環境の Postgres
ユーザー名とパスワードを入力ください。
DATABASE_URL=postgres://username:password@localhost/diesel_demo
PostgreSQL のインストールがまだの方はまずはこちらを実施ください。
ローカルに直接インストールする方法と、Docker コンテナで起動する方法があります。
🔻PostgresQL のダウンロード
🔻Docker イメージ
.env
ファイルを保存して、以下のdiesel
cli をコマンドラインで実行します。
$ diesel setup
Creating database: diesel_demo
これで diesel_demo
という名前のデータベースが作成されました。
さらにこの段階で、プロジェクト直下に、migrations
フォルダが生成され、
その中には 00000000000_diesel_initial_setup
フォルダがあり、up_sql
と down_sql
があることを確認します。
テーブルの作成
せっかくなので、テーブルも作成していきます。
コマンドラインで以下のように実行します。
$ diesel migration generate create_todos
migrations
フォルダに 2024-05-05-102854_create_todos
というフォルダが作成されました。
前半部分はタイムスタンプとなっているので、日時によって変わる部分です。
up_sql
に以下のようなテーブル作成用の記述をおこないます。
todos
テーブルを作成し、title
等のカラムが存在するテーブルを作成します。
-- Your SQL goes here
CREATE TABLE todos (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
completed BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
down.sql
には以下のような記述をしておきます。
-- This file should undo anything in `up.sql`
DROP TABLE todos;
コマンドラインで以下のように実行し、Running migration
と出れば成功です。
diesel migration run
Running migration 2024-05-05-102854_create_todos
マイグレーションを実行すると、src
フォルダ直下に、schema.rs
ファイルが作成されました。
// @generated automatically by Diesel CLI.
diesel::table! {
todos (id) {
id -> Int4,
#[max_length = 255]
title -> Varchar,
completed -> Bool,
created_at -> Timestamp,
}
}
DB への接続
main.rs
に schema.rs
のモジュールをインポートします。
pub mod schema;
さらにデータベースへの接続情報を記述。
これで、環境変数からURLを取得して、接続が可能となりました。
fn establish_connection() -> PgConnection {
dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
PgConnection::establish(&database_url)
.unwrap_or_else(|_| panic!("Error connecting to {database_url}"))
}
Rocket に反映
Rocket の Post リクエストで todo を登録する処理を記述します。
これで Post /todos の リクエストが来たら、Diesel経由でDBに書き込みが行われるようになりました。
#[macro_use]
extern crate rocket;
#[derive(FromForm, Insertable, Serialize, Deserialize, Debug)]
#[diesel(table_name = todos)]
struct Todo {
title: String,
completed: bool,
}
#[post("/todos", format = "json", data = "<todo>")]
fn create(todo: Json<Todo>) -> Status {
let mut connection = establish_connection();
let new_todo = Todo {
title: todo.title.clone(),
completed: todo.completed,
};
diesel::insert_into(todos::table)
.values(&new_todo)
.execute(&mut connection)
.expect("Error saving new todo");
Status::Created
}
#[launch]
fn rocket() -> _ {
rocket::build().mount("/", routes![create])
}
http://localhost:8000/todos
にアクセスし、以下のような Json 構造体を Body に定義します。
(プログラム上、completed
を定義していますが、これは別になくても良いと思いますが・・)
{
"title": "洗濯をする",
"completed": false
}
しっかりと、DBにデータが入っていることを確認できました。
最後に、全文を記載しておきます。
[package]
name = "hello-rocket"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rocket = { version = "0.5.0", features = ["json"] }
diesel = { version = "2.1.0", features = ["postgres"] }
dotenvy = "0.15"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
pub mod schema;
use rocket::{http::Status, serde::json::Json};
use schema::todos;
use serde::{Deserialize, Serialize};
use std::env;
use diesel::{Connection, Insertable, PgConnection, RunQueryDsl};
use dotenvy::dotenv;
fn establish_connection() -> PgConnection {
dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
PgConnection::establish(&database_url)
.unwrap_or_else(|_| panic!("Error connecting to {database_url}"))
}
#[macro_use]
extern crate rocket;
#[derive(FromForm, Insertable, Serialize, Deserialize, Debug)]
#[diesel(table_name = todos)]
struct Todo {
title: String,
completed: bool,
}
#[post("/todos", format = "json", data = "<todo>")]
fn create(todo: Json<Todo>) -> Status {
let mut connection = establish_connection();
let new_todo = Todo {
title: todo.title.clone(),
completed: todo.completed,
};
diesel::insert_into(todos::table)
.values(&new_todo)
.execute(&mut connection)
.expect("Error saving new todo");
Status::Created
}
#[launch]
fn rocket() -> _ {
rocket::build().mount("/", routes![create])
}
おわりに
いかがだったでしょうか。
最近、このようにいろんなフレームワークだったりライブラリだったりを触る機会がかなり多くなってきたので、Github にコミットしていこうかと考えています。(成果物にもなるし・・)
またアップして蓄積するようにしていきます!
Discussion