Rust で Todo ウェブアプリケーションを作ります。 新しく作り直しました。初回で一気に sqlite のデータベースに接続して、Create と Read を実装します。
[package]
name = "todo"
version = "0.1.0"
edition = "2024"
[dependencies]
askama = "0.14.0"
axum = "0.8.4"
tokio = { version = "1.0", features = ["full"] }
rusqlite = { version = "0.33.0", features = ["bundled"] }
tower-http = { version = "0.6.6", features = ["full"] }
serde = { version = "1.0.225", features = ["derive"] }
//******************************
// main.rs
// copyright : vivacocoa.jp
// last modified: Sep. 17. 2025
//******************************
use axum::{response::{Html, IntoResponse, Redirect}, routing::{get, post}, Router, Form};
use askama::Template;
use serde::Deserialize;
use tower_http::services::ServeDir;
#[derive(Template)]
#[template(path = "index.html")]
struct Todos
{
todos: Vec<Todo>,
}
#[derive(Clone)]
struct Todo
{
message: String,
}
#[derive(Deserialize)]
struct FormData {
message: String,
}
#[tokio::main]
async fn main() {
let connection = rusqlite::Connection::open("test.db").unwrap();
connection.execute("
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
todo TEXT
)
", []).unwrap();
let serve_dir = ServeDir::new("public");
let app = Router::new()
.nest_service("/static", serve_dir)
.route("/", get(index))
.route("/create", post(create));
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();
let addr = listener.local_addr().unwrap();
println!("Listening on {}", addr);
axum::serve(listener, app).await.unwrap();
}
async fn index() -> impl IntoResponse
{
let connection = rusqlite::Connection::open("test.db").unwrap();
let mut stat = connection.prepare("SELECT * FROM users").unwrap();
let mut rows = stat.query([]).unwrap();
let mut todos = Vec::new();
while let Some(row) = rows.next().unwrap() {
let todo: String = row.get(1).unwrap();
todos.push(Todo {message: todo});
}
let _ = todos.clone().into_iter().collect::<Vec<_>>();
let todos = Todos { todos };
Html(todos.render().unwrap()).into_response()
}
async fn create(Form(data): Form<FormData>) -> Redirect
{
let connection = rusqlite::Connection::open("test.db").unwrap();
connection. execute ( "INSERT INTO users (todo) VALUES (?1)", (data.message,)). unwrap ();
Redirect::to("/")
}
プロジェクトのルートディレクトリに templates というディレクトリを作り、 その中に次の index.html を作ります。
<!-----------------------------
index.html
copyright : vivacocoa.jp
last modified: Sep. 17, 2025
------------------------------>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Todo</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="container">
<h1>Todo</h1>
<form method="post" action="/create">
<div class="h-layout">
<input type="text" name="message" required maxlength="30">
<button class="btn-black">Create</button>
</div>
</form>
{%for todo in todos %}
<p class="todo">{{ todo.message }}</p>
{% endfor %}
</div>
</body>
</html>
プロジェクトのルートディレクトリに public というディレクトリを作り、 その中に次の style.css を作ります。
/*****************************
style.css
copyright : vivacocoa.jp
last modified: Sep. 16, 2025
******************************/
body {
background-color: #efefef;
}
.container {
width: 500px;
margin: 16px auto;
}
h1 {
font-size: 20px;
}
.todo {
height: 80px;
width: 100%;
border-radius: 4px;
padding: 0px 8px;
line-height: 80px;
box-sizing: border-box;
background-color: white;
margin-bottom: 0px;
}
.h-layout {
display: flex;
}
input {
height: 25px;
width: 100%;
padding: 4px;
border: solid 0px #efefef;
border-radius: 4px;
}
.btn-black {
background-color: black;
width: 60px;
color: white;
border-radius: 4px;
}
ターミナルでプロジェクトのルートディレクトリに移動して、cargo run とコマンドしてサーバーを起動してください。 そしてお使いのブラウザで localhost:3000 もしくは 127.0.0.1:3000 を開いてください。