まずテーブル作成
$ 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
なんだこれは…