まずテーブル作成
$ sudo -u postgres psql
$ CREATE TABLE “todo” (
id UUID primary key,
description varchar(100) not null,
deadline_at timestamp not null
);
postgres=# \dt
List of relations
Schema | Name | Type | Owner
——–+——–+——-+———-
public | todo | table | postgres
chrono = "0.4.31"
sqlx = { version = "0.7.2", features = ["runtime-tokio-native-tls", "chrono", "uuid"]}
uuid = { version = "1.6.1", fatures = ["v4", "serde"] }
use axum::{Router, extract::State, response::Html, routing::get};
use tera::{Context, Tera};
use serde::{Serialize, Deserialize};
use sqlx::{FromRow, PgPool};
use uuid::Uuid;
//
#[derive(Clone)]
struct ServiceState {
tera: Tera,
pool: PgPool,
}
//
#[tokio::main]
async fn main() {
let pool = PgPool::connect("postgres://postgres:password@localhost:5432/postgres").await.unwrap();
let tera = match Tera::new("templates/**/*.html"){
Ok(t) => t,
Err(e) => {
println!("Parsing error(s): {}", e);
::std::process::exit(1);
}
};
let app = Router::new()
.route("/", get(index))
.with_state(ServiceState {tera, pool});
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
http://192.168.33.10:3000/create_todo

### 入力結果の表示
templates/todos.html
<a href="/create_todo">Todoを作成</a>
<table>
<thread>
<tr>
<th scope="col">description</th>
<th scope="col">deadline at</th>
</tr>
</thread>
<tbody>
{% for todo in todos %}
<tr id="{{ todo.id }}">
<td>{{ todo.description }}</td>
<td>{{ todo.deadline_at }}</td>
</tr>
{% endfor %}
</tbody>
</table>
use axum::{Router, extract::State, response::{Redirect, Html}, Form, routing::get};
use chrono::NaiveDateTime;
use tera::{Context, Tera};
use serde::{Serialize, Deserialize};
use sqlx::{FromRow, PgPool};
use uuid::Uuid;
pub fn deserialize_date<'de, D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<NaiveDateTime, D::Error> {
let s = String::deserialize(deserializer)?;
NaiveDateTime::parse_from_str(&s, "%Y-%m-%dT%H:%M")
.map_err(serde::de::Error::custom)
}
#[derive(Serialize)]
struct Index {
name: String
}
#[derive(Clone)]
struct ServiceState {
tera: Tera,
pool: PgPool,
}
#[derive(Debug, Serialize, FromRow)]
struct Todo {
id: Uuid,
description: String,
deadline_at: NaiveDateTime,
}
#[derive(Debug, Deserialize)]
struct CreateTodo {
description: String,
#[serde(deserialize_with = "deserialize_date")]
deadline_at: NaiveDateTime,
}
async fn index(State(state): State<ServiceState>) -> Html<String> {
let index = Index { name: String::from("test") };
let page = state.tera.render("index.html", &Context::from_serialize(&index).unwrap()).unwrap();
Html(page.to_owned())
}
async fn get_create_todo(State(state): State<ServiceState>) -> Html<String> {
let page = state.tera.render("create_todo.html", &Context::new()).unwrap();
Html(page.to_owned())
}
async fn post_create_todo(
State(state): State<ServiceState>,
Form(todo): Form<CreateTodo>,
) -> Redirect {
let todo = Todo {
id: Uuid::new_v4(),
description: todo.description,
deadline_at: todo.deadline_at,
};
sqlx::query("INSERT INTO todo VALUES ($1, $2, $3);")
.bind(todo.id)
.bind(todo.description)
.bind(todo.deadline_at)
.execute(&state.pool)
.await
.expect("todoの取得に失敗しました");
Redirect::to("/todos")
}
async fn get_todos(
State(state): State<ServiceState>,
) -> Html<String> {
let todos = sqlx::query_as::<_, Todo>("SELECT * FROM todo")
.fetch_all(&state.pool)
.await
.expect("todoの取得に失敗しました");
let mut context = Context::new();
context.insert("todos", &todos);
let page = state.tera.render("todos.html", &context).expect("todoの描画に失敗しました");
Html(page)
}
#[tokio::main]
async fn main() {
let pool = PgPool::connect("postgres://postgres:password@localhost:5432/postgres").await.unwrap();
let tera = match Tera::new("templates/**/*.html"){
Ok(t) => t,
Err(e) => {
println!("Parsing error(s): {}", e);
::std::process::exit(1);
}
};
let app = Router::new()
.route("/", get(index))
.route("/todos", get(get_todos))
.route("/create_todo", get(get_create_todo).post(post_create_todo))
.with_state(ServiceState {tera, pool});
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
http://192.168.33.10:3000/todos
Todoを作成
description deadline at
test 2025-01-01T15:54:00
aaa 2025-01-01T15:57:00
なんだこれは…