読み飛ばしてください
どうも限界派遣SESのnikawamikanです。
アドカレ24日目です。
今回は、RustのWebフレームワークLocoを試してみましたが、公式チュートリアルをなぞっているだけなので気になる方は公式サイトを見るのをオススメします。
Locoとは
LocoとはRuby on RailsみたいにCLIで部品を生成して、Webアプリケーションを作成できるRustのWebフレームワークです。
名前の由来もRailsのlocomotiveから来ているようで、Railsのような使い勝手を提供してくれるようです。
しかし筆者はRailsを使ったことがないので、どれだけRailsっぽいのかはわかりませんが、CLIで部品を生成して、CRUDの実装が簡単にできるのは便利そうです。
なお、公式サイトによると、Node.jsの10倍速いらしいです。
環境構築
まずはRust環境ですが、例に漏れずDevContainerを使って環境構築しました。
開発コンテナの構成ファイルをGUIからポチポチ作っていくと、Rust & PostgreSQLの環境が用意されているので、これを利用します。
設定ファイルが出力されたら、以下のようなファイル構成になっていると思います。
.devcontainer
├── devcontainer.json
├── docker-compose.yml
├── Dockerfile
└── .env
devcontainer.json
のpostCreateCommand
にLocoのインストールコマンドを追加します。
こうすることで、コンテナが起動したときに自動でLocoがインストールされるようになります。
dbのポートも開放しておくと便利かと思います。
{
"name": "Rust and PostgreSQL",
"dockerComposeFile": "docker-compose.yml",
"service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
"forwardPorts": [
"db:5432"
],
"postCreateCommand": "cargo install loco && cargo install sea-orm-cli",
}
また、Locoではデータベースを使って開発する場合、ローカルの開発環境のDB設定をpostgres://loco:loco@localhost:5432/myapp_development
のように設定する必要があるので、.env
ファイルを作成しておきます。
POSTGRES_DB
はアプリケーション名+_development
になるので、適宜変更してください。
POSTGRES_USER=loco
POSTGRES_PASSWORD=loco
POSTGRES_DB=myapp_development
POSTGRES_HOSTNAME=localhost
POSTGRES_PORT=5432
あとは、コンテナを起動すれば自動で開発環境が構築されます。
チュートリアルに沿ってアプリケーションを作成
今回はチュートリアルに沿って色々ためしてみます。
プロジェクトの作成
コンテナが起動したら、アプリケーションを作成します。
コンソールから以下のコマンドを実行すると対話形式でアプリケーションの設定ができます。
4つの質問に答えるだけでアプリケーションを作成できます。
loco new
今回は以下のように設定しました。
✔ ❯ App name? · myapp
✔ ❯ What would you like to build? · Saas App with server side rendering
✔ ❯ Select a DB Provider · Postgres
✔ ❯ Select your background worker type · Async (in-process tokio async tasks)
🚂 Loco app generated successfully in:
/workspaces/loco_project/testes
- database: You've selected `postgres` as your DB provider (you should have a postgres instance to connect to)
これで、プロジェクトが生成されるので、cd
コマンドで移動して、アプリケーションを起動します。
cd myapp
cargo loco start
結構ビルドに時間がかかるみたいなので、気長に待ちましょう。
ビルドが完了すると、以下のようなログが出力されます。
かわいいですね。
▄ ▀
▀ ▄
▄ ▀ ▄ ▄ ▄▀
▄ ▀▄▄
▄ ▀ ▀ ▀▄▀█▄
▀█▄
▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▀▀█
██████ █████ ███ █████ ███ █████ ███ ▀█
██████ █████ ███ █████ ▀▀▀ █████ ███ ▄█▄
██████ █████ ███ █████ █████ ███ ████▄
██████ █████ ███ █████ ▄▄▄ █████ ███ █████
██████ █████ ███ ████ ███ █████ ███ ████▀
▀▀▀██▄ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ██▀
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
https://loco.rs
environment: development
database: automigrate
logger: debug
compilation: debug
modes: server
listening on http://localhost:5150
http://localhost:5150
にアクセスしてみると、以下のような画面が表示されます。
これで起動まで確認できました。
コントローラーの作成
次にコントローラーを作成してみます。
cargo loco generate controller guide --api
これでsrc/controller/guide.rs
が生成されます。
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::unnecessary_struct_initialization)]
#![allow(clippy::unused_async)]
use loco_rs::prelude::*;
use axum::debug_handler;
#[debug_handler]
pub async fn index(State(_ctx): State<AppContext>) -> Result<Response> {
format::text("hello")
}
pub fn routes() -> Routes {
Routes::new()
.prefix("api/guides/")
.add("/", get(index))
}
これで、localhost:5150/api/guides
にアクセスすると、hello
と表示されるようになります。
$ curl http://localhost:5150/api/guides
hello
モデルの作成
ガイドに沿ってArticle
モデルを作成してみます。
cargo loco generate model article title:string body:text
これで、src/model/_entities/article.rs
とsrc/model/article.rs
が生成されます。
src/model/_entities/article.rs
はBDスキーマを定義しているファイルで、これは変更してはいけないようです。
ロジックはsrc/model/article.rs
に書くようです。
自動で生成されたファイルは以下のようになっています。
use sea_orm::entity::prelude::*;
use super::_entities::articles::{ActiveModel, Entity};
pub type Articles = Entity;
#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
// extend activemodel below (keep comment for generators)
async fn before_save<C>(self, _db: &C, insert: bool) -> std::result::Result<Self, DbErr>
where
C: ConnectionTrait,
{
if !insert && self.updated_at.is_unchanged() {
let mut this = self;
this.updated_at = sea_orm::ActiveValue::Set(chrono::Utc::now().into());
Ok(this)
} else {
Ok(self)
}
}
}
また同時にマイグレーションが行われているようで、DBを確認するとarticles
テーブルが作成されていました。
これで、モデルの作成が完了しました。
CRUDの実装
先ほどのMODELを使ってCRUDの実装をしてみます。
これもコマンド一発で生成できます。
cargo loco generate scaffold article
これで、src/controller/article.rs
が生成されますが、Updateの実装は自分で行う必要があるので、以下のように実装します。
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Params {
pub title: Option<String>,
pub content: Option<String>,
}
impl Params {
fn update(&self, item: &mut ActiveModel) {
item.title = Set(self.title.clone());
item.content = Set(self.content.clone());
}
}
これだけで、CRUDの実装が完了します。殆どのコードが自動で生成されているので、非常に簡単に実装できます。
自動で生成されたルーティングは以下のようになっています。
pub fn routes() -> Routes {
Routes::new()
.prefix("api/articles/")
.add("/", get(list))
.add("/", post(add))
.add(":id", get(get_one))
.add(":id", delete(remove))
.add(":id", put(update))
.add(":id", patch(update))
}
動作確認
試しにデータを登録して、取得してみます。
curl -X POST -H "Content-Type: application/json" -d '{
"title":"Locoくんすごいね",
"content":"俺、コード全然かいてねえよ・・・。かなしい・・・。"
}' localhost:5150/api/articles
curl localhost:5150/api/articles
[{"created_at":"2024-12-24T14:52:36.440264Z","updated_at":"2024-12-24T14:52:36.440264Z","id":5,"title":"Locoくんすごいね","content":"俺、コード全然かいてねえよ・・・。かなしい・・・。"}]
ついでに、Updateも試してみます。
curl -X PUT -H "Content-Type: application/json" -d '{
"title":"Locoくんすごいね",
"content":"コードかかなくても動いてるよ!すごいね!"
}' localhost:5150/api/articles/5
curl localhost:5150/api/articles/5
{"created_at":"2024-12-24T14:52:36.440264Z","updated_at":"2024-12-24T14:55:18.474649Z","id":5,"title":"Locoくんすごいね","content":"コードかかなくても動いてるよ!すごいね!"}
最後に、Deleteも試してみます。
curl -X DELETE localhost:5150/api/articles/5
curl localhost:5150/api/articles/5
{"error":"not_found","description":"Resource was not found"}
簡単にCRUDの実装ができましたね。
まとめ
今回は、RustのWebフレームワークLocoを試してみました。
正直、この記事読むより公式のチュートリアルのほうがわかりやすいので、興味がある方は公式サイトを見てみてください。
本当はもうすこし具体的なアプリ開発をしたかったのですが、cargo loco
コマンドを実行する度にコンパイルが走るので、思ったよりも時間がかかってしまいました。
それでは。
参考