前回までで、 Axum + Askama + SQLite の組み合わせで CRUD を実装しました。CSS で画面も整えています。 しかし、これを通常の方法でサーバーに公開した場合、IP もしくは ドメイン:3000 という形でアクセスすることになります。 これを IP もしくは ドメイン/askama というアドレスでアクセスできるようにします。
このコーナーでは、データベースとして 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
# Windows にインストールする
SQLite をインストールすることはできますが、デベロッパーライブラリのインストールにはまだ成功していません。
ライブラリのインストールは不要との説もありますが、実際には動作しません。
[package]
name = "askama"
version = "0.1.0"
edition = "2024"
[dependencies]
askama = "0.14.0"
axum = "0.8.4"
tokio = { version = "1.0", features = ["full"] }
tower-http = { version = "0.6.6", features = ["full"] }
serde = { version = "1.0.225", features = ["derive"] }
rusqlite = { version = "0.37.0", features = ["bundled"] }
プロキシーサーバを使わない場合から3箇所変更しています。
//******************************
// main.rs
// copyright : vivacocoa.jp
// last modified: Oct. 02. 2025
//******************************
use axum::{response::{Html, IntoResponse, Redirect}, routing::{get, post}, Router, Form};
use askama::Template;
use serde::Deserialize;
use tower_http::services::ServeDir;
use rusqlite::params;
#[derive(Template)]
#[template(path = "index.html")]
struct Todos
{
todos: Vec<Todo>,
}
#[derive(Clone)]
struct Todo
{
id: u64,
message: String,
}
#[derive(Deserialize)]
struct FormData {
id: u64,
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))
.route("/update", post(update))
.route("/delete", post(delete));
let listener = tokio::net::TcpListener::bind("0.0.0.0: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 i_d: u64 = row.get(0).unwrap();
let todo: String = row.get(1).unwrap();
todos.push(Todo {message: todo, id: i_d});
}
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 ();
let _ = connection.close().unwrap();
Redirect::to("/askama") // ここを変更しています
}
async fn update(Form(data): Form<FormData>) -> Redirect
{
let _msg = data.message;
let _id = data.id;
let connection = rusqlite::Connection::open("test.db").unwrap();
let _ = connection.execute("UPDATE users SET todo = ?1 WHERE id = ?2", params![_msg, _id],);
let _ = connection.close().unwrap();
Redirect::to("/askama") // ここを変更しています
}
async fn delete(Form(data): Form<FormData>) -> Redirect
{
let connection = rusqlite::Connection::open("test.db").unwrap();
let _ = connection.execute("DELETE FROM users WHERE id = ?", (data.id,));
let _ = connection.close().unwrap();
Redirect::to("/askama") // ここを変更しています
}
プロジェクトのルートディレクトリに public というディレクトリを作り、 その中に次の style.css を作ってください。
プロキシサーバーを使わない場合から変更はありません。
/*****************************
style.css
copyright : vivacocoa.jp
last modified: Sep. 20, 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;
}
.todo2 {
height: 35px;
width: 100%;
border-radius: 4px;
padding: 0px 8px;
line-height: 80px;
box-sizing: border-box;
background-color: #efefef;
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;
}
.h-layout-2 {
display: flex;
position: relative;
}
.btn-white {
background-color: white;
border: solid 1px lightgray;
border-radius: 4px;
color: gray;
width: 50px;
height: 30px;
position: absolute;
right: 4px;
bottom: 4px;
}
.btn-white2 {
background-color: white;
border: solid 1px lightgray;
border-radius: 4px;
color: gray;
width: 60px;
height: 30px;
position: absolute;
right: 0px;
top: 10px;
}
.btn-white3 {
background-color: white;
border: solid 1px lightgray;
border-radius: 4px;
color: gray;
width: 55px;
height: 30px;
position: absolute;
right: 65px;
top: 10px;
}
プロジェクトのルートディレクトリに templates というディレクトリを作り、 その中に次の index.html を作ります。
プロキシサーバーを使わない場合から5箇所変更になっています
<!-----------------------------
index.html
copyright : vivacocoa.jp
last modified: Oct. 02, 2025
------------------------------>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Askama</title>
<link rel="stylesheet" href="/askama/static/style.css"> <!-- 変更しています -->
<scritp src="/askama/static/staticposition.js"></script> <!-- 追加しています -->
</head>
<body>
<div class="container">
<h1>Askama</h1>
<form method="post" action="/askama/create"> <!-- 変更しています -->
<div class="h-layout">
<input type="text" name="message" required maxlength="30">
<input type="hidden" name="id" value="1">
<button class="btn-black">Create</button>
</div>
</form>
{%for todo in todos %}
<div class="h-layout-2">
<p class="todo" id=p{{todo.id}}>{{ todo.message }}</p>
<button type="button" class="btn-white" onclick="toggle('elm{{todo.id}}')">Edit</button>
</div>
<div id=elm{{todo.id}} hidden class="todo2">
<div class="h-layout-2">
<form method="post" action="/askama/update" id=f{{todo.id}}> <!-- 変更しています -->
<input type="hidden" name="id" value={{todo.id}}>
<input type="hidden" name="message" value={{todo.message}} id=i{{todo.id}}>
<button hidden>submit</button>
</form>
<button type="submit" class="btn-white2" id=b{{todo.id}}
onclick="updatebtn('i{{todo.id}}', 'f{{todo.id}}', '{{todo.message}}')">Update</button>
<form method="post" action="/askama/delete"> <!-- 変更しています -->
<input type="hidden" name="id" value={{todo.id}}>
<input type="hidden" name="message" value={{todo.message}}>
<button class="btn-white3" onclick="return confirm('削除してよろしいですか?');">Delete</button>
</form>
</div>
</div>
{% endfor %}
</div>
<script>
function toggle(elm) {
const e = document.getElementById(elm);
e.hidden = !e.hidden;
}
function updatebtn(i, f, msg) {
var msg = prompt('アップデートを入力してください。', msg)
if (msg !== null && msg !== '') {
document.getElementById(i).setAttribute('value', msg);
document.getElementById(f).submit();
}
}
</script>
</body>
</html>
public ディレクトリに次の staticposition.js を作ります。
プロキシサーバーを使わない場合から追加されています。ページをリロードした時に、 ページトップに戻らないようにするためのスクリプトです。
history.scrollRestoration = "manual";
window.addEventListener("load", () => {
const scrollPosition = sessionStorage.getItem("scrollPosition");
if (scrollPosition) {
window.scrollTo(0, parseInt(scrollPosition));
}
});
window.addEventListener("scroll", () => {
sessionStorage.setItem("scrollPosition", window.scrollY.toString());
});
window.addEventListener("beforeunload", () => {
sessionStorage.setItem("scrollPosition", window.scrollY.toString());
});
GitHub 経由でサーバーにデプロイします。
(1) ローカルからリモートへアップロードします。
git init
git add .
git commit -m "任意の文字列"
git branch -M main
git remote add origin https://github.com/あなたのアカウント名/リポジトリー名.git
git push -u origin main
(2) リモートからローカルへダウンロードします。
git clone https://github.com/あなたのアカウント名/リポジトリー名.git
(3) ダウンロードしたディレクトリに移動して次のようにビルドしてくださお。
cargo build --release
アドレス:8080 という形ではなく、アドレス/アプリケーション名でアクセスしたくなると思います。 その場合は、apach などの Web サーバーを立ち上げて、プロキシを利用します。 ポート 3000 の場合は適宜読み替えてください。
apache2 の場合は、/etc/apache2/apache2.conf に次のコードを追加してください。
httpd の場合は /etc/httpd/conf/httpd.conf に次のコードを追加してください。
追加する場所はファイルの一番最後で大丈夫です。
<Location "/アプリケーション名">
ProxyPass http://0.0.0.0:8080
ProxyPassReverse http://0.0.0.0:8080
</Location>
# アプリケーション名のところは tera とか askama です。
# 0.0.0.0 ではなく正規の IP でも大丈夫ですが、0.0.0.0 の方が色々と対応できて便利です。
<Location "/違うアプリケーション名">
ProxyPass http://0.0.0.0:3000
ProxyPassReverse http://0.0.0.0:3000
</Location>
apache2.conf を書き換えた後は、apache2 を再起動します。
# Apache2 の場合
sudo service apache2 restart
# Httpd の場合
sudo systemctl restart httpd
ポートを開放して、新しい Askama アプリケーションを起動します。
# ポートの使用状況の確認
sudo lsof -i :3000
# 古い tera が起動していたら、その PID 番号で古い askama を終了します。
sudo kill -9 PID番号
# askama_proxy ディレクトリに移動して、新しい askama を起動します。
nohup ./target/release/askama &
# もし何かのログが表示されたら、コードのどこかが間違っています。ビルドから修正してやり直してください。
これで、サーバーのIPアドレス/アプリケーション名もしくはドメイン/アプリケーション名でアクセスできるようになります。
なおプロキシはいくつでも書き足すことができます。