Docs Menu
Docs Home
/ / /
Rust ドライバー
/ /

チュートリアル: Rocket でCRUD Web アプリを作成

このチュートリアルでは、 Rocket ウェブフレームワークを使用してRustウェブアプリケーションを作成する方法を学びます。 Rustドライバーを使用すると、メモリ管理、有効期間、データベースプーリングなどの機能を活用して、アプリケーションのパフォーマンスを向上させることができます。

このチュートリアルを完了すると、 CRUD操作を実行するためのルートを含むウェブアプリケーションが作成されます。

Tip

完全なアプリ

このチュートリアルで作成されたアプリの完全なバージョンを表示するには、GitHub の mongodb-api-rsリポジトリ にアクセスしてください。

開発環境にRust 1.71.1 以降とRustパッケージマネージャーの Rust がインストールされていることを確認してください。

Rust および Cardgo をインストールする方法の詳細については、 Rust の ダウンロードとインストールに関する Rust の公式ガイド を参照してください。

また、 MongoDB Atlasクラスターを設定する必要があります。クラスターの作成方法については、 クイック スタートガイドの MongoDB配置の作成 ステップを参照してください。接続文字列を安全な場所に保存して、チュートリアルの後半で使用します。

1

次のコマンドを実行して、rust-crud-appプロジェクトディレクトリを作成し、入力します。

cargo new rust-crud-app
cd rust-crud-app
2

次のコマンドを実行して、 Rustドライバーを追加します。

cargo add mongodb

Cargo.tomlファイルにRustドライバーの次のエントリが含まれていることを確認します。

[dependencies]
mongodb = "3.2.4"
3

Rustドライバーを使用する場合は、同期ランタイムまたは非同期ランタイムのいずれかを選択する必要があります。このチュートリアルでは、API を構築するのに適した非同期ランタイムを使用します。

ドライバーはデフォルトで非同期tokio ランタイムで動作します。

利用可能なランタイムの詳細については、「 非同期 API と同期 API に関するガイド 」を参照してください。

4

Atlas UIにアクセスし、クラスター設定で [Collections]タブを選択します。 + Create Database ボタンを選択します。 [] モーダルで、bread というデータベースを作成し、その内に recipes というコレクションを作成します。

5

INSERT DOCUMENTボタンを選択し、サンプルアプリリポジトリに別の string_data.jsonファイルの内容を貼り付けます。

データを挿入すると、 recipesコレクションにサンプルドキュメントが表示されます。

6

IDE を開き、プロジェクトディレクトリを入力します。プロジェクトルートから次のコマンドを実行して、Rocket Webフレームワークをインストールします。

cargo add -F json rocket

Cargo.tomlファイルの依存関係リストに rocket のエントリが含まれていることを確認します。

また、Rocket によって開発されたcrateを追加する必要があります。これにより、 MongoDBクライアントによって行われる非同期接続のコレクションプールをラッパーを使用して管理できます。このcrateを使用すると、 MongoDB のデータベースとコレクションをパラメーター化し、各アプリ関数が独自の接続を受け取って使用することができます。

次のコマンドを実行して、rocket_db_pools crateを追加します。

cargo add -F mongodb rocket_db_pools

Cargo.tomlファイルの依存関係リストに、mongodb の機能フラグを含む rocket_db_pools のエントリが含まれていることを確認します。

7

breadデータベースを使用するように Ruby を構成するには、プロジェクトルートに Rocket.toml というファイルを作成します。構成設定を読み取るためにこのファイルを検索します。このファイルにMongoDB接続文字列を保存することもできます 。

次の構成を Rocket.toml に貼り付けます。

[default.databases.db]
url = "<connection string>"

Rocket の構成の詳細については、Rocket ドキュメントの「 構成 」を参照してください。

8

API の記述を開始する前に、単純な Rocketアプリの構造について学習し、アプリケーション内に対応するファイルを作成します。

次の図は、Rockeアプリに必要なファイル構造を示し、各ファイルの機能を説明しています。

.
├── Cargo.lock # Dependency info
├── Cargo.toml # Project and dependency info
├── Rocket.toml # Rocket configuration
└── src # Directory for all app code
├── db.rs # Establishes database connection
├── main.rs # Starts the web app
├── models.rs # Organizes data
└── routes.rs # Stores API routes

前の図に従って、srcディレクトリとそれに含まれるファイルを作成します。この点で、ファイルは空にできます。

9

以下のコードをdb.rs ファイルに貼り付けてください。

use rocket_db_pools::{mongodb::Client, Database};
#[derive(Database)]
#[database("db")]
pub struct MainDatabase(Client);

また、データベース構造体 を Rocketインスタンスにアタッチする必要があります。 main.rs では、次のコードに示すように、データベースを初期化し、アタッチします。

mod db;
mod models;
mod routes;
use rocket::{launch, routes};
use rocket_db_pools::Database;
#[launch]
fn rocket() -> _ {
rocket::build()
.attach(db::MainDatabase::init())
.mount()
}

IDE では、mount() に引数が欠落しているというエラーが発生する場合があります。後の手順でルートを追加するため、このエラーは無視できます。

10

データを表すための一貫した有用な構造体を定義することは、型の安全性を維持し、ランタイムエラーを減らすために重要です。

models.rsファイルには、ケーキのレシピを表す Recipe 構造体を定義します。

use mongodb::bson::oid::ObjectId;
use rocket::serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct Recipe {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
pub id: Option<ObjectId>,
pub title: String,
pub ingredients: Vec<String>,
pub temperature: u32,
pub bake_time: u32,
}
11

ルーティングにより、プログラムはリクエストを適切なエンドポイントに送信し、データを送受信できます。ファイルroutes.rs にはAPIで定義されたすべてのルートが保存されています。

次のコードを routes.rsファイルに追加して、インデックスルートと単純な get_recipes() ルートを定義します。

use crate::db::MainDatabase;
use crate::models::Recipe;
use mongodb::bson::doc;
use rocket::{futures::TryStreamExt, get, serde::json::Json};
use rocket_db_pools::{mongodb::Cursor, Connection};
#[get("/")]
pub fn index() -> Json<Value> {
Json(json!({"status": "It is time to make some bread!"}))
}
#[get("/recipes", format = "json")]
pub async fn get_recipes(db: Connection<MainDatabase>) -> Json<Vec<Recipe>> {
let recipes: Cursor<Recipe> = db
.database("bread")
.collection("recipes")
.find(None, None)
.await
.expect("Failed to retrieve recipes");
Json(recipes.try_collect().await.unwrap())
}

残りのルートを作成する前に、Rocke のメイン起動関数にルートを追加します。

main.rs で、ファイルが次のコードのようになるように引数を mount() に置き換えます。

mod db;
mod models;
mod routes;
use rocket::{launch, routes};
use rocket_db_pools::Database;
#[launch]
fn rocket() -> _ {
rocket::build().attach(db::MainDatabase::init()).mount(
"/",
routes![
routes::index,
routes::get_recipes,
routes::create_recipe,
routes::update_recipe,
routes::delete_recipe,
routes::get_recipe
],
)
}
12

アプリでは、 CRUD操作の予期しない結果に対応するために、エラー処理とカスタム応答を実装する必要があります。

次のコマンドを実行中して serde_json crateをインストールします。

cargo add serde_json

このcrateには、 JSON値を表す Value列挙が含まれています。

Rocket の status::Custom 構造体を使用して、ルートがHTTPステータス コードを返すように指定できます。これにより、 HTTPステータス コードと返すカスタム データを指定できます。次の手順では、status::Custom 型を返すルートを記述する方法について説明します。

13

MongoDBでデータを作成しようとすると、次の 2 つの結果が考えられます。

  • ドキュメントは正常に作成されたため、アプリはHTTP 201 を返します。

  • 挿入中にエラーが発生したため、アプリはHTTP 400 を返します。

次のルートを routes.rsファイルに追加して、create_recipe() ルートを定義し、エラー処理を実装します。

#[post("/recipes", data = "<data>", format = "json")]
pub async fn create_recipe(
db: Connection<MainDatabase>,
data: Json<Recipe>,
) -> status::Custom<Json<Value>> {
if let Ok(res) = db
.database("bread")
.collection::<Recipe>("recipes")
.insert_one(data.into_inner(), None)
.await
{
if let Some(id) = res.inserted_id.as_object_id() {
return status::Custom(
Status::Created,
Json(json!({"status": "success", "message": format!("Recipe ({}) created successfully", id.to_string())})),
);
}
}
status::Custom(
Status::BadRequest,
Json(json!({"status": "error", "message":"Recipe could not be created"})),
)
}

MongoDBからデータを読み込もうとすると、次の 2 つの結果が考えられます。

  • 一致するドキュメントのベクトルを返します。

  • 一致するドキュメントがないかエラーが発生したため、空のベクトルを返します。

これらの結果が予想されるため、get_recipes() ルートを次のコードで置き換えます。

#[get("/recipes", format = "json")]
pub async fn get_recipes(db: Connection<MainDatabase>) -> Json<Vec<Recipe>> {
let recipes = db
.database("bread")
.collection("recipes")
.find(None, None)
.await;
if let Ok(r) = recipes {
if let Ok(collected) = r.try_collect::<Vec<Recipe>>().await {
return Json(collected);
}
}
return Json(vec![]);
}

get_recipe()update_recipe()delete_recipe()サンプルアプリリポジトリの route.rsファイルから 、 、 ルートをコピーできます。

14

ターミナルで次のコマンドを実行中してアプリケーションを起動します。

cargo run

別のターミナルウィンドウで、次のコマンドを実行して、create_recipe() ルートをテストします。

curl -v --header "Content-Type: application/json" --request POST --data '{"title":"simple bread recipe","ingredients":["water, flour"], "temperature": 250, "bake_time": 120}' http://127.0.0.1:8000/recipes
{"status":"success","message":"Recipe (684c4245f5a3ca09efa92593) created successfully"}

次のコマンドを実行して、get_recipes() ルートをテストします。

curl -v --header "Content-Type: application/json" --header "Accept: application/json" http://127.0.0.1:8000/recipes/
[{"_id":...,"title":"artisan","ingredients":["salt","flour","water","yeast"],"temperature":404,"bake_time":5},
{"_id":...,"title":"rye","ingredients":["salt"],"temperature":481,"bake_time":28},...]

次のコマンドを実行して、delete_recipe() ルートをテストします。 <id> プレースホルダーを、コレクション内の既知の _id 値に置き換えます。これは 68484d020f561e78c03c7800 のようになります。

curl -v --header "Content-Type: application/json" --header "Accept: application/json" --request DELETE http://127.0.0.1:8000/recipes/<id>
{"status":"","message":"Recipe (68484d020f561e78c03c7800) successfully deleted"}

このチュートリアルでは、Rocke を使用してCRUD操作を実行するための簡単なウェブアプリケーションを構築する方法を学習しました。

CRUD操作の詳細については、次のガイドを参照してください。

戻る

複合演算子