目次
- はじめに
- 前提条件
- プロジェクトのセットアップ
- MongoDBドライバのインストール
- Actixウェブフレームワークの導入
- MongoDBへの接続
- ToDoモデルの作成
- CRUD操作の実装
- APIエンドポイントの作成
- フロントエンドの実装
- エラーハンドリング
- テストの作成
- デプロイメント
- パフォーマンスの最適化
- セキュリティの考慮事項
- 結論
1. はじめに
この記事では、RustプログラミングとMongoDBを使用して、ウェブベースのToDoリストアプリケーションを作成する方法を詳しく説明します。バックエンドにはActixウェブフレームワークを使用し、フロントエンドには簡単なHTMLとJavaScriptを使用します。
2. 前提条件
このチュートリアルを進めるには、以下のものが必要です:
- Rustの基本的な知識
- 最新のRustツールチェーン(バージョン1.57+)
- MongoDBの基本的な知識とローカルにインストールされたMongoDBサーバー
- コードエディタ(VS CodeやIntelliJ Rustなど)
- ウェブ開発の基本的な知識
3. プロジェクトのセットアップ
新しいRustプロジェクトを作成します。
cargo new todo_list_web
cd todo_list_web
Cargo.toml
ファイルに必要な依存関係を追加します。
[dependencies]
actix-web = "4.0"
mongodb = "2.0"
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
dotenv = "0.15"
thiserror = "1.0"
4. MongoDBドライバのインストール
MongoDBのRustドライバをインストールします。
cargo add mongodb
5. Actixウェブフレームワークの導入
Actixウェブフレームワークを導入します。src/main.rs
に以下のコードを追加します。
use actix_web::{web, App, HttpServer, HttpResponse};
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/", web::get().to(|| HttpResponse::Ok().body("Hello, Todo List!")))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
6. MongoDBへの接続
MongoDBに接続するためのコードを作成します。src/db.rs
ファイルを作成し、以下のコードを追加します。
use mongodb::{Client, Database};
use dotenv::dotenv;
use std::env;
pub async fn connect_to_db() -> mongodb::error::Result<Database> {
dotenv().ok();
let uri = env::var("MONGODB_URI").expect("MONGODB_URI must be set");
let client = Client::with_uri_str(&uri).await?;
Ok(client.database("todo_db"))
}
7. ToDoモデルの作成
ToDoアイテムのモデルを作成します。src/models.rs
ファイルを作成し、以下のコードを追加します。
use serde::{Deserialize, Serialize};
use mongodb::bson::oid::ObjectId;
#[derive(Debug, Serialize, Deserialize)]
pub struct Todo {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
pub id: Option<ObjectId>,
pub title: String,
pub completed: bool,
}
8. CRUD操作の実装
CRUD操作を実装します。src/repository.rs
ファイルを作成し、以下のコードを追加します。
use mongodb::{Collection, Database};
use mongodb::bson::{doc, oid::ObjectId};
use crate::models::Todo;
pub struct TodoRepository {
collection: Collection<Todo>,
}
impl TodoRepository {
pub fn new(db: Database) -> Self {
let collection = db.collection("todos");
TodoRepository { collection }
}
pub async fn create_todo(&self, title: &str) -> mongodb::error::Result<ObjectId> {
let new_todo = Todo {
id: None,
title: title.to_string(),
completed: false,
};
let result = self.collection.insert_one(new_todo, None).await?;
Ok(result.inserted_id.as_object_id().unwrap())
}
pub async fn get_todos(&self) -> mongodb::error::Result<Vec<Todo>> {
let mut cursor = self.collection.find(None, None).await?;
let mut todos = Vec::new();
while let Some(todo) = cursor.try_next().await? {
todos.push(todo);
}
Ok(todos)
}
pub async fn update_todo(&self, id: &ObjectId, completed: bool) -> mongodb::error::Result<()> {
self.collection
.update_one(doc! { "_id": id }, doc! { "$set": { "completed": completed } }, None)
.await?;
Ok(())
}
pub async fn delete_todo(&self, id: &ObjectId) -> mongodb::error::Result<()> {
self.collection.delete_one(doc! { "_id": id }, None).await?;
Ok(())
}
}
9. APIエンドポイントの作成
APIエンドポイントを作成します。src/handlers.rs
ファイルを作成し、以下のコードを追加します。
use actix_web::{web, HttpResponse, Responder};
use crate::repository::TodoRepository;
use crate::models::Todo;
use mongodb::bson::oid::ObjectId;
pub async fn create_todo(repo: web::Data<TodoRepository>, todo: web::Json<Todo>) -> impl Responder {
match repo.create_todo(&todo.title).await {
Ok(id) => HttpResponse::Created().json(id),
Err(_) => HttpResponse::InternalServerError().finish(),
}
}
pub async fn get_todos(repo: web::Data<TodoRepository>) -> impl Responder {
match repo.get_todos().await {
Ok(todos) => HttpResponse::Ok().json(todos),
Err(_) => HttpResponse::InternalServerError().finish(),
}
}
pub async fn update_todo(repo: web::Data<TodoRepository>, id: web::Path<String>, todo: web::Json<Todo>) -> impl Responder {
let object_id = ObjectId::parse_str(&id).unwrap();
match repo.update_todo(&object_id, todo.completed).await {
Ok(_) => HttpResponse::Ok().finish(),
Err(_) => HttpResponse::InternalServerError().finish(),
}
}
pub async fn delete_todo(repo: web::Data<TodoRepository>, id: web::Path<String>) -> impl Responder {
let object_id = ObjectId::parse_str(&id).unwrap();
match repo.delete_todo(&object_id).await {
Ok(_) => HttpResponse::NoContent().finish(),
Err(_) => HttpResponse::InternalServerError().finish(),
}
}
10. フロントエンドの実装
簡単なフロントエンドを実装します。static
ディレクトリを作成し、その中にindex.html
とapp.js
ファイルを作成します。
index.html
:
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ToDoリスト</title>
</head>
<body>
<h1>ToDoリスト</h1>
<input type="text" id="todo-input" placeholder="新しいタスクを入力">
<button onclick="addTodo()">追加</button>
<ul id="todo-list"></ul>
<script src="app.js"></script>
</body>
</html>
app.js
:
async function getTodos() {
const response = await fetch('/todos');
const todos = await response.json();
const todoList = document.getElementById('todo-list');
todoList.innerHTML = '';
todos.forEach(todo => {
const li = document.createElement('li');
li.textContent = todo.title;
li.onclick = () => toggleTodo(todo._id, !todo.completed);
if (todo.completed) {
li.style.textDecoration = 'line-through';
}
todoList.appendChild(li);
});
}
async function addTodo() {
const input = document.getElementById('todo-input');
const title = input.value.trim();
if (title) {
await fetch('/todos', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({title})
});
input.value = '';
getTodos();
}
}
async function toggleTodo(id, completed) {
await fetch(`/todos/${id}`, {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({completed})
});
getTodos();
}
getTodos();
11. エラーハンドリング
エラーハンドリングを改善します。src/error.rs
ファイルを作成し、以下のコードを追加します。
use thiserror::Error;
#[derive(Error, Debug)]
pub enum TodoError {
#[error("Database error: {0}")]
DatabaseError(#[from] mongodb::error::Error),
#[error("Invalid ID: {0}")]
InvalidId(String),
}
12. テストの作成
単体テストと統合テストを作成します。tests
ディレクトリを作成し、その中にintegration_tests.rs
ファイルを作成します。
#[cfg(test)]
mod tests {
use actix_web::{test, web, App};
use crate::handlers;
use crate::repository::TodoRepository;
use crate::db::connect_to_db;
#[actix_rt::test]
async fn test_get_todos() {
let db = connect_to_db().await.unwrap();
let repo = web::Data::new(TodoRepository::new(db));
let app = test::init_service(
App::new()
.app_data(repo.clone())
.route("/todos", web::get().to(handlers::get_todos))
).await;
let req = test::TestRequest::get().uri("/todos").to_request();
let resp = test::call_service(&app, req).await;
assert!(resp.status().is_success());
}
}
13. デプロイメント
アプリケーションをデプロイする方法について説明します。例えば、DockerとHerokuを使用したデプロイメント手順を提供します。
14. パフォーマンスの最適化
アプリケーションのパフォーマンスを最適化する方法について説明します。例えば、インデックスの使用、キャッシング、非同期処理の活用などを紹介します。
15. セキュリティの考慮事項
セキュリティに関する考慮事項について説明します。例えば、入力のバリデーション、認証と認可、HTTPS の使用、CORSの設定などを紹介します。
16. 結論
このチュートリアルでは、RustとMongoDBを使用してウェブベースのToDoリストアプリケーションを作成する方法を学びました。
Actixウェブフレームワークを使用してバックエンドを構築し、簡単なフロントエンドを実装しました。さらに、エラーハンドリング、テスト、デプロイメント、パフォーマンス最適化、セキュリティについても触れました。
このプロジェクトを基に、さらに機能を追加したり、より複雑なアプリケーションを構築したりすることができます。
RustとMongoDBの組み合わせは、高性能で信頼性の高いウェブアプリケーションを開発するための強力なツールセットです。
ぜひ、このチュートリアルを参考に、独自のアイデアを実現してください。Rustでのウェブ開発を楽しんでください!