🕌

Rust Diesel を使って PostgresQL にアクセスする

2024/05/06に公開

はじめに

今回は Diesel という Rust の ORM ライブラリを触ってみたいと思います。
最終的に、Rocket という web フレームワークに Http リクエストをして、Diesel からアクセスするというところまで行きたいと思います。

🔻Diesel
https://diesel.rs/

🔻Rocket
https://rocket.rs/

初期構築

プロジェクトの作成

任意の Rust プロジェクトを作成しておきます。
改めてになりますが、今回は既存のプロジェクトを使用します。
以下の記事にて、Rust の Rocketを使ったプロジェクトを用意する手順がありますので、参考までに。
(短時間で構築できる内容となっていますので、ぜひ。)
https://zenn.dev/collabostyle/articles/f606940fb6fe7c

Cargo.toml の更新

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を環境変数に定義します。
usernamepasswordの部分はご自身のローカル環境の Postgresユーザー名とパスワードを入力ください。

.env
DATABASE_URL=postgres://username:password@localhost/diesel_demo
PostgreSQL のインストールがまだの方はまずはこちらを実施ください。

ローカルに直接インストールする方法と、Docker コンテナで起動する方法があります。
🔻PostgresQL のダウンロード
https://www.postgresql.jp/download

🔻Docker イメージ
https://hub.docker.com/_/postgres

.envファイルを保存して、以下のdieselcli をコマンドラインで実行します。

$ diesel setup

Creating database: diesel_demo

これで diesel_demoという名前のデータベースが作成されました。

さらにこの段階で、プロジェクト直下に、migrationsフォルダが生成され、
その中には 00000000000_diesel_initial_setupフォルダがあり、up_sqldown_sqlがあることを確認します。

テーブルの作成

せっかくなので、テーブルも作成していきます。
コマンドラインで以下のように実行します。

$ diesel migration generate create_todos

migrationsフォルダに 2024-05-05-102854_create_todosというフォルダが作成されました。
前半部分はタイムスタンプとなっているので、日時によって変わる部分です。

up_sqlに以下のようなテーブル作成用の記述をおこないます。
todosテーブルを作成し、title等のカラムが存在するテーブルを作成します。

up_dql
-- 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には以下のような記述をしておきます。

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ファイルが作成されました。

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.rsschema.rsのモジュールをインポートします。

main.rs
pub mod schema;

さらにデータベースへの接続情報を記述。
これで、環境変数からURLを取得して、接続が可能となりました。

main.rs
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に書き込みが行われるようになりました。

main.rs
#[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にデータが入っていることを確認できました。

最後に、全文を記載しておきます。

Cargo.toml
[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"
main.rs
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 にコミットしていこうかと考えています。(成果物にもなるし・・)

またアップして蓄積するようにしていきます!

コラボスタイル Developers

Discussion