Make the MongoDB docs better! We value your opinion. Share your feedback for a chance to win $100.
Click here >
Docs Menu
Docs Home
/ /

Rocket を使用したCRUD Web アプリ

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

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

Tip

完全なアプリ

このチュートリアルの完全なサンプルアプリを表示するには、GitHub の web-app-tutorial フォルダーを参照してください。

開発環境にRust 1.74 以降と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.6.0"
3

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

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

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

4

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

5

INSERT DOCUMENT ボタンを選択し、サンプルアプリの cake_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ファイルに貼り付けます。

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

また、データベース構造体 を Rocketインスタンスにアタッチする必要があります。次のテンプレート コードを main.rsファイルにコピーします。

src/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())
// Paste mount() call below
}

IDE では関数本体が不完全であるというエラーが発生する場合があります。後の手順で mount() 呼び出しを追加するため、このエラーは無視できます。

10

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

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

src/models.rs
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ファイルにコピーします。

src/ route.rs
use crate::db::MainDatabase;
use crate::models::Recipe;
use mongodb::bson::doc;
use mongodb::bson::oid::ObjectId;
use rocket::{
delete, futures::TryStreamExt, get, http::Status, post, put, response::status,
serde::json::Json,
};
use rocket_db_pools::Connection;
use serde_json::{json, Value};
// Paste index route below
// Paste get_recipes route below
// Paste get_recipe route below
// Paste update_recipe route below
// Paste delete_recipe route below
// Paste create_recipe route below

次の index ルートを // Paste index route below コメントの下に貼り付けます。

#[get("/")]
pub fn index() -> Json<Value> {
Json(json!({"status": "It is time to make some bread!!!"}))
}

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

main.rsファイルで、// Paste mount() call below コメントの下に次のコードを貼り付けます。

.mount(
"/",
routes![
routes::index,
routes::get_recipes,
routes::create_recipe,
routes::get_recipe,
routes::update_recipe,
routes::delete_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 を返します。

次の create_recipe() ルートを routes.rsファイルの // Paste create_recipe route below コメントの下に貼り付けます。

src/ route.rs
#[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() ルートを routes.rsファイルの // Paste get_recipes route below コメントの下に貼り付けます。

src/ route.rs
#[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() ルートを // Paste get_recipe route below コメントの下に貼り付けます。

src/ route.rs
#[get("/recipes/<id>", format = "json")]
pub async fn get_recipe(
db: Connection<MainDatabase>,
id: &str,
) -> status::Custom<Json<Value>> {
let b_id = ObjectId::parse_str(id);
if b_id.is_err() {
return status::Custom(
Status::BadRequest,
Json(json!({"status": "error", "message":"Recipe ID is invalid"})),
);
}
if let Ok(Some(recipe)) = db
.database("bread")
.collection::<Recipe>("recipes")
.find_one(doc! {"_id": b_id.unwrap()}, None)
.await
{
return status::Custom(
Status::Ok,
Json(json!({"status": "success", "data": recipe})),
);
}
return status::Custom(
Status::NotFound,
Json(json!({"status": "success", "message": "Recipe not found"})),
);
}

このルートは、_id 値で 1 つのドキュメントを検索します。

次の update_recipe() ルートを // Paste update_recipe route below コメントの下に貼り付けます。

src/ route.rs
#[put("/recipes/<id>", data = "<data>", format = "json")]
pub async fn update_recipe(
db: Connection<MainDatabase>,
data: Json<Recipe>,
id: &str,
) -> status::Custom<Json<Value>> {
let b_id = ObjectId::parse_str(id);
if b_id.is_err() {
return status::Custom(
Status::BadRequest,
Json(json!({"status": "error", "message":"Recipe ID is invalid"})),
);
}
if let Ok(_) = db
.database("bread")
.collection::<Recipe>("recipes")
.update_one(
doc! {"_id": b_id.as_ref().unwrap()},
doc! {"$set": mongodb::bson::to_document(&data.into_inner()).unwrap()},
None,
)
.await
{
return status::Custom(
Status::Created,
Json(
json!({"status": "success", "message": format!("Recipe ({}) updated successfully", b_id.unwrap())}),
),
);
};
status::Custom(
Status::BadRequest,
Json(
json!({"status": "success", "message": format!("Recipe ({}) could not be updated successfully", b_id.unwrap())}),
),
)
}

このルートは、単一のドキュメントを _id 値で更新します。

次の delete_recipe() ルートを // Paste delete_recipe route below コメントの後に貼り付けます。

src/ route.rs
#[delete("/recipes/<id>")]
pub async fn delete_recipe(
db: Connection<MainDatabase>,
id: &str,
) -> status::Custom<Json<Value>> {
let b_id = ObjectId::parse_str(id);
if b_id.is_err() {
return status::Custom(
Status::BadRequest,
Json(json!({"status": "error", "message":"Recipe ID is invalid"})),
);
}
if db
.database("bread")
.collection::<Recipe>("recipes")
.delete_one(doc! {"_id": b_id.as_ref().unwrap()}, None)
.await
.is_err()
{
return status::Custom(
Status::BadRequest,
Json(
json!({"status": "error", "message":format!("Recipe ({}) could not be deleted", b_id.unwrap())}),
),
);
};
status::Custom(
Status::Accepted,
Json(
json!({"status": "", "message": format!("Recipe ({}) successfully deleted", b_id.unwrap())}),
),
)
}

このルートは、_id 値で 1 つのドキュメントを削除します。

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操作の詳細については、次のガイドを参照してください。

戻る

大きなファイルの保存