Rust | Axum と SQLx & PostgreSQL で Todo アプリを作る
Axum と SQLx
Rust の Web フレームワークである Axum と SQL クレートである SQLx を使って、Todo アプリを作って見ました。
ソースコードは Github においてあります。参考までに、どうぞ!!
Axum
実装したエンドポイントは以下です。
- GET /v1/todos
- POST /v1/todos
- PATCH /v1/todos/:id
- PUT /v1/todos/:id
- DELETE /v1/todos/:id
Router
Router
を生成し、それぞれの HTTP メソッドに該当する関数に実行するハンドラ関数を引数として渡しています。
layer
には DI コンテナとして振る舞うModules
をExtension
として渡して、各ハンドラ関数から Modules
を呼び出せるようにします。
URL クエリパラメータを受け取る
Query(query): Query<TodoQuery>
でクエリパラメータを受け取ることができます。
なお、構造体への変換に Serde の Deserialize が必要です。
URL パスパラメータ・JSON リクエストの受け取る
URLパスパラメータをPath(id): Path
で、JSON リクエストをValidatedRequest(source): ValidatedRequest<JsonUpdateTodoContents>
でそれぞれ受け取ることができます。
バリデーション
バリデーションに関するヘルパー等は、context に定義してあります。
validate
で返ってきた結果をerrors
という配列に格納して、JSON で返却するためにJsonErrorResponse
を定義しています。
型が異なる等で JSON リクエストを定義した構造体に変換する際にエラーが発生する場合、Json::::from_request(req).await?
が先に実行されるため、独自に実装したバリデーションのエラーより先にAppError::JsonRejection
エラーが返ります。
validator クレートのマクロを使って、バリデーションを実装しています。
required
は Option 型のみで使用できます。そのため、別の構造体に詰め替える処理の中でバリデーションを通過した正常値である前提でunwrap
を行っています。
title
をOption<String>
からString
に変更し、リクエストで null を送信した場合は、AppError::JsonRejection
エラーが返ることになります。
バリデーションの実装については、Axum exaples の validator も合わせてご確認いただくと良いかと思います。
SQLx
REST API のエンドポイントと内部で実行される SQL は以下のようになってます。
- GET /v1/todos -> SELECT
- POST /v1/todos -> INSERT
- PATCH /v1/todos/:id -> UPDATE
- PUT /v1/todos/:id -> UPSERT
- DELETE /v1/todos/:id -> DELETE
PostgreSQL と接続する
コネクションをはるために、Pool
を生成します。
connect
に DB 接続用の URL を渡しています。
PostgreSQL の場合、postgresql://username:password@host:port/database
です。
query_as で SELECT
fetch_one
ID 指定などで、SELECT の結果が1件の場合は、fetch_one
を使います。
返り値はquery_as
で指定した構造体Struct
のOption
です。
where句のt.id = $1
の$1
はbind
で Todo レコードの ID を反映させます。
fetch_all
複数件の結果を取得する場合は、fetch_all
を使います。
返り値はquery_as
で指定した構造体Struct
のOption<Vec>
です。
URL クエリパラメータで検索するステータスを指定できます。
ステータスの指定があれば、該当するステータスのみの Todo を取得するように実装しました。
query で INSERT
query
とexecute
で INSERT を実行できます。
$1
から$3
の順番にbind
すれば OK です。
query で UPDATE
INSERT と同じで、query
とexecute
で UPDATE を実行できます。
PATCH /v1/todos/:id の処理であるため、引数がNone
の場合は更新しないようにしています。
query で UPSERT
INSERT や UPDATE と同じで、query
とexecute
で UPSERT[1] を実行できます。
UPSERT 自体は、PostgreSQL のON CONFLICT DO UPDATE
を使って実装しています。
query で DELETE
INSERT や UPDATE と同じで、query
とexecute
で DELETE を実行できます。
削除した Todo の情報をレスポンスとして返したいので、ここでは削除前に SELECT を入れています。
まとめ
SQL を書くことに抵抗がない場合、ORM も楽に実装できる気がします。
もちろん、ORM には ORM の良さがあるので、採用時にはしっかりと検討する必要があります。
また、SQLx にもマイグレーション機能があるようなので、そちらも触っていきたいです。
Todo アプリとしては、ユーザーと紐づけて管理できるところぐらいまでは実装したいです。
参考
以下のブログでも、記事を公開しています。よければ遊びに来てください😽
-
UPDATE or INSERT(レコードがあれば更新、なければ作成) ↩︎
Discussion