Loco入門:RustにおけるRailsへの回答
Railsに着想を得たRustフレームワーク、LocoでWebアプリを構築。CLIの導入、CRUD APIの生成、バックグラウンドワーカーの追加をLoco 1.2で解説。
Locoは、Railsに着想を得たRust製Webフレームワークです。Axumによるルーティング、Sea-ORMのモデルとマイグレーション、バックグラウンドワーカー、コードジェネレーターを、cargo locoという単一のコマンドで扱えます。
ビジネスロジックを1行も書く前に、Axum、ORM、マイグレーションランナー、ジョブキューをつなぎ合わせるだけで週末が終わってしまった経験はないでしょうか。Locoは、まさにその手間を省くために設計されています。本ガイドでは、Locoのインストール、APIアプリの作成、リソースのスキャフォールド、バックグラウンドジョブの追加までを解説します。古いチュートリアルの多くは1.0より前のバージョンを対象としているため、本ガイドのコマンドはすべてLoco 1.2.0の1.x系CLIに準拠しています。Rustを初めて学ぶ方は、まずRustの基礎からお読みください。
重要なポイント
- LocoはRuby on Railsをモデルにした Rust製Webフレームワークです。ルーティングにはAxum、モデルとマイグレーションにはSea-ORM 2.0を採用しています。
- CLIは
cargo install locoでインストールします。cargo install sea-orm-cliも必要ですが、アプリでデータベースを使用する場合に限られます。 - Loco 1.2では、
cargo loco generate scaffoldがマイグレーション、モデル、エンティティ、DTO、CRUDコントローラーを生成し、続けてマイグレーションを適用します。個別のマイグレーション手順は不要です。 - 新規作成したLocoアプリの開発サーバーは、ポート5150で待ち受けます。
- 開発時、バックグラウンドジョブは
BackgroundAsyncモードによりプロセス内で実行されます。本番環境では通常、Redis、Postgres、またはSQLiteのキューに移行し、cargo loco start --workerで処理します。
RustのLocoとは?
Locoは、規約重視(opinionated)のフルスタックRustフレームワークです。素のAxumプロジェクトでは開発者に委ねられるスタック構成の判断を、フレームワーク側で引き受けてくれます。Locoの公式サイトによれば、HTTPレイヤーにはAxum、ORMにはSea-ORM 2.0が採用されています。さらに、ワーカー、スケジューラー、メーラー、ストレージ、キャッシュも組み込まれています。プロジェクト構成、設定システム、CLIがそれぞれ1つに統一されているため、5つものクレートを選定し、それらをつなぐコードを自分で書く必要はありません。ソースコードはGitHubで公開されています。Locoは2026年7月に初の安定版である1.0に到達しました。
LocoはRailsのどの考え方を取り入れているのか?
LocoはRailsの4つの慣習を取り入れています。どのプロジェクトも同じ予測可能な構成を持ち、ボイラープレートはジェネレーターが書き、スキーマ変更はバージョン管理されたマイグレーションを通じて行い、時間のかかる処理はバックグラウンドジョブに回す、というものです。DjangoやLaravelを使ったことがあれば、名前は違えど同じ考え方に触れているはずです。
| コンセプト | 意味 | Loco 1.2のコマンドまたは配置場所 |
|---|---|---|
| 設定より規約(Convention over configuration) | ファイルはフレームワークが想定する場所に配置される | src/およびconfig/配下に生成される構成 |
| ジェネレーター | CLIがボイラープレートコードを生成する | cargo loco generate scaffold、model、controller、worker |
| マイグレーション | バージョン管理されたスキーマ変更 | migration/src/、generate時に自動適用 |
| バックグラウンドジョブ | リクエストサイクルの外で処理を実行する | cargo loco generate worker、perform_later |
Locoのインストールとアプリの作成方法
Locoのインストールはcargoコマンド2つ、アプリの作成はさらに1つのコマンドで完了します。LocoのREADMEにあるとおり、sea-orm-cliのインストールはアプリでデータベースを使用する場合にのみ必要です。今回の例ではSQLiteを使用するため、インストールしておきます。
cargo install loco
cargo install sea-orm-cli
次に、アプリを生成します。4つのフラグをすべて指定すると、ウィザードの対話プロンプトがスキップされるため、スクリプト化が可能になり、毎回同じ結果が得られます。
loco new --name tour_app --db sqlite --bg async --assets none
cd tour_app
cargo loco start
公式のファーストアプリチュートリアルによると、--db sqliteはデータベースにSQLiteを選択し、--bg asyncはバックグラウンドジョブをプロセス内で実行し、--assets noneはビューのスキャフォールドを省略するため、アプリはJSONのみを返すようになります。起動時の出力の最終行で、サーバーが立ち上がったことを確認できます。
listening on port 5150
デフォルトでは、開発サーバーはポート5150で待ち受けます。設定リファレンスに示されているとおり、設定ファイルではPORT環境変数を読み取るget_env呼び出しでポートが指定されているため、ここで変更できます。また、データベースを選択したことで、アプリには/api/auth/*配下の認証ルートも標準で組み込まれています。
古いチュートリアルではloco-cliというクレートをインストールしています。Loco 1.0でこのクレートは削除され、現在CLIはlocoとしてインストールします。
スキャフォールドでリソースを生成する方法
Locoのスキャフォールドを使うと、データベースと連携して動作するCRUD APIを1つのコマンドで生成できます。
cargo loco generate scaffold posts title:string! content:text --no-auth
Loco 1.2では、cargo loco generate scaffoldがマイグレーション、Sea-ORMのモデルとエンティティ、型付きのリクエスト/レスポンスDTO、CRUDコントローラーを生成します。その後マイグレーションを適用してエンティティを再生成するため、マイグレーションコマンドを別途実行する必要はありません。
フィールドの記法はモデル追加ガイドに基づいています。title:string!のように型の末尾に!を付けると必須(NOT NULL)カラムになります。content:textのように型だけを指定するとNULL許容カラムになり、^を付けるとユニーク制約が付与されます。スキャフォールドで生成されたルートはデフォルトでJWTを要求するため、未認証のリクエストには401が返ります。--no-authフラグを指定するとルートが公開されるため、今回のデモではこれを使ってシンプルにしています。
生成されたプロジェクトのうち、本ガイドで使用する部分は次のとおりです(一部抜粋)。
tour_app/
├── config/development.yaml
├── migration/src/
└── src/
├── app.rs
├── controllers/posts.rs
├── dtos/
├── models/_entities/
└── workers/
新しいエンドポイントをテストするには、サーバーを再起動してPOSTリクエストを送信します。
curl -X POST -H "Content-Type: application/json" \
-d '{"title":"Tour post","content":"hi"}' localhost:5150/api/posts
JSONレスポンスは{"id":1,から始まり、保存された投稿の内容がそのまま返されます。
Locoでバックグラウンドジョブを追加する方法
Locoのバックグラウンドジョブは、型付きの引数構造体を持つワーカーモジュールです。perform_laterでキューに登録します。まずはワーカーを生成しましょう。
cargo loco generate worker notifier
added: "src/workers/notifier.rs"
injected: "src/workers/mod.rs"
injected: "src/app.rs"
バックグラウンドワーカーガイドで説明されているとおり、ジェネレーターは構造体の名前を常にWorkerとし、独立したモジュールに配置します。その隣には、自分で中身を記述する空のWorkerArgs構造体が置かれます。つまり、生成されたnotifierワーカーはworkers::notifier::Workerとなります。src/workers/notifier.rsでWorkerArgsにフィールドを追加し、performの本体を書き換えます。buildをはじめ、生成されたファイルのその他の部分はそのままにしておきます。
#[derive(Deserialize, Debug, Serialize)]
pub struct WorkerArgs {
pub post_id: i64,
}
// inside the generated `impl BackgroundWorker<WorkerArgs> for Worker`
async fn perform(&self, args: WorkerArgs) -> Result<()> {
println!("new post created: {}", args.post_id);
Ok(())
}
次にsrc/controllers/posts.rsを開き、スキャフォールドで生成されたcreateハンドラー内の、新しい投稿を挿入する行(let item = item.insert(&ctx.db).await?;)の直後に以下のコードを追加します。Loco 1.0以降、生成される主キーはi64であるため、item.idは型変換なしでpost_idに渡せます。
crate::workers::notifier::Worker::perform_later(
&ctx,
crate::workers::notifier::WorkerArgs { post_id: item.id },
)
.await?;
Loco 1.0のリリースノートに記載されているとおり、Loco 1.0以降のperform_later()はジョブIDをResult<String>として返します。後からジョブを追跡したい場合は、これをログに出力しておくとよいでしょう。
config/development.yamlではworkers.modeがBackgroundAsyncに設定されており、ジョブはプロセス内のTokioタスクとして実行されます。BackgroundAsyncモードでは、キューのバックエンドは一切不要です。本番環境では、workers.modeをBackgroundQueueに切り替え、Redis、Postgres、またはSQLiteのキューを設定したうえで、cargo loco start --workerを使って別プロセスでジョブを処理します。PostgresとSQLiteのキューはデフォルトのworkerフィーチャーで動作しますが、Redisキューにはworker_redisが必要です。バックエンドは設定ファイルのqueue.kindで選択します。切り替えを行っても、ワーカーやコントローラーのコードを変更する必要はありません。
Locoはどのような開発者に適しているか?
LocoはRustによるバックエンド構築を大幅に高速化しますが、Rustでコードを書くこと自体のコストがなくなるわけではありません。
- コンパイル時間。 変更のたびに
rustcを通す必要があるため、編集してリロードするサイクルはRuby、Python、PHPのフレームワークより遅くなります。 - エコシステム。 サードパーティ製のインテグレーションやプラグイン、解決済みのQ&Aの数は、RailsやDjangoが積み上げてきたものに比べてはるかに少ないのが現状です。
- 学習曲線。 生成されたコードでも、所有権、async、トレイトを使いこなせることが前提となっており、ハンドラーで発生するコンパイラエラーも読み解ける前提で出力されます。
Locoは、すでにRustを習得していてRails並みのセットアップ速度を求める開発者や、意図してRustに移行しようとしているRails、Django、Laravelの開発者に適しています。一方、Webサーバーの仕組みとRustを同時に学ぼうとしている場合、初めてのバックエンドとして選ぶのには向いていません。Web開発のためにRustを学ぶ価値があるか迷っている方は、Web開発者にRustは必要かをお読みください。
まとめ
Locoは、RailsがRuby開発者にもたらすものをRust開発者にもたらします。つまり、単一のCLI、予測可能なプロジェクト構成、組み込みのマイグレーションとジョブを、すべてAxumとSea-ORMの上で提供します。古いチュートリアルにある1.0以前のコマンドではなく、本ガイドで紹介した1.x系のコマンドを使用してください。次のステップとして、cargo loco generate modelで2つ目のリソースを追加し、続いてバックグラウンドワーカーガイドに沿って、キューをBackgroundAsyncから永続的なバックエンドへ移行してみましょう。
よくある質問
既存のAxumミドルウェアやエクストラクターをLocoアプリで使用できますか?
はい。LocoはAxumを包む「グルーコード」が大部分を占めるため、FromRequestPartsを実装したカスタムエクストラクターはLocoのコントローラーでもそのまま動作します。tower-httpのタイムアウトやセッションレイヤーなどのTowerレイヤーを適用するには、src/app.rsでHooks::after_routesを実装します。このフックは、Locoが構築済みのAxumルーターを受け取ります。
LocoアプリのデータベースをSQLiteからPostgresに切り替えるには?
config/development.yamlまたはconfig/production.yamlのdatabase.uriに、postgres://形式の接続文字列を設定します。生成された設定ファイルはget_envを通じてDATABASE_URL変数から値を読み取るため、この環境変数を設定する方法でも構いません。Locoのデフォルトでは、ユーザー名loco、パスワードloco、そしてmyapp_developmentのようにアプリ名と環境名を組み合わせたデータベース名が想定されています。URIにsslmode=requireを追加するとPostgresのTLSが有効になり、追加のCargoフィーチャーは不要です。
Locoアプリを本番環境にデプロイするには?
cargo build --releaseでリリースバイナリをビルドし、config/フォルダとともにサーバーへコピーします。デプロイ先のマシンにRustツールチェーンやcargoは不要です。パッケージングには、cargo loco generate deploymentでdocker、nginx、lambdaの3種類を選択できます。dockerはDockerfileと.dockerignoreを、nginxはリバースプロキシの設定を生成し、lambdaはAWS Lambdaのエントリーポイントを生成してlambda_httpの依存関係を追加します。本番環境の設定はconfig/production.yamlで管理します。
Locoのスケジューラーとバックグラウンドワーカーの違いは何ですか?
ワーカーはコードからperform_laterでキューに登録されたジョブを実行し、スケジューラーは決められたスケジュールに従ってジョブを実行します。スケジューラーのジョブは設定ファイルのYAMLで宣言し、それぞれにcron式または平易な英語表現でスケジュールを指定します。runの値には登録済みのLocoタスク名を指定し、shellがtrueの場合はシェルコマンドを指定します。--schedulerフラグを使用すると、ワーカープロセスを起動せずに、サーバーと並行してスケジューラーを実行できます。
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k