Actix-web + Tera + SQLite の組み合わせで Create と Read を実装しました。
リロードするとチェックボックスのチェックが外れるという問題が残っていますが、修正するのに時間がかかりそうなのと、 修正すると、ルートとハンドラーが増えて、コードが複雑になり、読みにくくなると思うでの、ここでいったんアップすることにしました。
2025年10月11日 index.html を少し変更しました。動作に違いはありませんが、コードを読みやすくしただけです。
2025年9月29日 この節を追加しました。
この Rust のコーナーでは、Laravel に続き、データベースとして SQLite を使っています。 SQLite を次のようにしてインストールしてください
# Debian 系にインストールする
sudo apt install sqlite3
sudo apt install libsqlite3-dev
# RPM 系にインストールする
sudo dnf install sqlite
sudo dnf install sqlite-devel
# macOS にインストールする、次のコマンドだけでデベロッパーライブラリもインストールされます。
brew install sqlite
<
2025年10月18日 この節を追加しました。
2025年10月21日 ARM版の場合も追加しました。
Windows の場合は次のサイトからダウンロードします。
SQLite Download Page手順は次のとおりです。
lib /DEF:sqlite3.def /OUT:sqlite3.lib /MACHINE:x64
ARM 版の場合は次のようにします。
lib /DEF:sqlite3.def /OUT:sqlite3.lib
MACHIN 指定がありません。ARM64 だと推定します。というような表示があると思います。
[package]
name = "tera"
version = "0.1.0"
edition = "2024"
[dependencies]
actix-web = "4.11.0"
rusqlite = "0.37.0"
serde = { version = "1.0.226", features = ["derive"] }
tera = "1.20.0"
use actix_web::{web, App, Responder, HttpResponse, HttpServer};
use actix_web::web::Redirect;
use tera::Tera;
use rusqlite::{Connection, params};
use serde::{Serialize, Deserialize};
// Todoのデータ構造を定義
#[derive(Serialize, Deserialize)]
struct Todo {
title: String,
completed: bool,
}
#[actix_web::main]
async fn main() -> std::io::Result<()>
{
let conn = Connection::open("todo.db"); // データベースファイルを開く、なければ作成する
// テーブルが存在しない場合に作成する
let _ = conn.expect("REASON").execute(
"CREATE TABLE IF NOT EXISTS todos (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
completed BOOLEAN NOT NULL
)",
[],
);
HttpServer::new(|| {
// templatesディレクトリ内のHTMLを読み込む
let tera = Tera::new("templates/**/*").expect("Failed to initialize Tera");
App::new()
.app_data(web::Data::new(tera))
.service(web::resource("/").to(index))
.service(web::resource("/create").to(create))
// 他のルート(更新、削除など)も追加できる
})
.bind("127.0.0.1:8080")?
.run()
.await
}
async fn index(tera: web::Data<Tera>) -> impl Responder
{
let conn = Connection::open("todo.db").unwrap();
let mut stat = conn.prepare("SELECT * FROM todos").unwrap();
let mut rows = stat.query([]).unwrap();
let mut todos = Vec::new();
while let Ok(Some(row)) = rows.next() {
let todo: String = row.get(1).unwrap();
let comp: bool = row.get(2).unwrap();
todos.push(Todo {title: todo, completed: comp});
}
let mut context = tera::Context::new();
context.insert("todos", &todos);
let rendered = tera.render("index.html", &context).unwrap(); // index.htmlをレンダリング
HttpResponse::Ok().body(rendered)
}
async fn create(form: web::Form<Todo>) -> Redirect
{
let conn = rusqlite::Connection::open("todo.db").unwrap();
conn. execute ( "INSERT INTO todos (title, completed) VALUES (?1, ?2)", params![form.title, form.completed],).unwrap();
Redirect::to("/")
}
プロジェクトのルートディレクトリに templates というディレクトリを作り、 その中に次の index.html を作ります。
2025年10月11日 少し変更しました。動作に違いはありませんが、コードを読みやすくしただけです。
<!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>
<form method="post" action="/create">
<input type="text" name="title" required maxlength="30">
<input type="hidden" name="completed" value="false">
<button>Create</button>
</form>
<ul>
{% for todo in todos %}
<li>
<input type="checkbox" {% if todo.completed %}checked{% endif %}>
{% if todo.completed %}
<del>{{ todo.title }}</del>{% else %}{{ todo.title }}
{% endif %}
</li>
{% else %}
<li>No todo</li>
{% endfor %}
</ul>
</body>
</html>
ターミナルでプロジェクトのルートディレクトリに移動して、cargo run とコマンドしてサーバーを起動してください。 そしてお使いのブラウザで localhost:8080 もしくは 127.0.0.1:8080 を開いてください。