📚

自分用の蔵書管理アプリを作る

2022/12/10に公開

この記事は個人開発Advent Calendar 2022 10日目の記事です。

はじめに

自分用のWebアプリケーションを作る際に考えたことをまとめておきたいと思います。誰かの参考になれば幸いです。

アプリケーションの概要

自分のための蔵書管理アプリを開発しています。本を登録したり、優先度や既読状況をもとにフィルタリングして次に読む本を探したりできます。

こちらのREADMEからデモアプリケーションにアクセスできます。

https://github.com/hiterm/bookshelf

開発理由

もともとは単純に自分のための蔵書管理アプリが欲しくなったという理由で作り始めました。既存のサービスはいくつかあるのですが、自分の欲しい機能がすべて提供されているわけではありません。それならばいっそのこと自分で作ってしまおう、ということで開発を始めました。

また学習も開発目的の一つです。仕事で携わるのは、既存プロダクトの機能追加や、新規開発であってもチーム開発で一部分の実装だけ、というのがほとんどではないでしょうか。個人開発であれば、技術選定から実装まで一人で経験を積むことができます。

開発期間

最初のコミットが2020年6月なので、2022/12/10現在で2年以上開発が続いているようです。

v1

ソースコード

https://github.com/hiterm/bookshelf/tree/v1

技術選定

技術スタック

矢印は変化したものです。

  • TypeScript
  • React
  • Vite
    • Create React App → Vite
  • MUI
    • v4 → v5
  • React Hook Form
    • Formik → React Hook Form
  • Firebase Auth
  • Firestore
  • Vercel
    • Firebase Hosting → Vercel

アプリの形態

ネイティブアプリケーションもしくはWebアプリケーションが選択肢でした。以下のような理由でWebアプリケーションにしました。

  • スマホでもPCでも使いたい
  • スマホとPCで別々のネイティブアプリを作るのはコストがかかる
  • Webフロントエンド技術に興味があった

言語、ライブラリ、フレームワーク

Vueも検討しましたが、以下の理由でReactを選択しました。

  • Reactの方がTypeScriptとの相性が良いとされている
  • 世界的シェアはReactの方が高い

次に、Next.jsを利用するのか、React単体でCSRの構成にするのかを検討しました。CSRを選択しました。CSRの場合、基本的には静的ファイルをホスティングするだけなので、デプロイ先の選択肢が増えるだろうと考えたためです。

フレームワークについては当初はCreate React Appを用いていました。しかし途中でよりビルドの速いViteに乗り換えました。

データベース

以下の理由でFirestoreを選択しました。

  • 無料で利用できる
    • クレジットカードの登録も不要
  • フロントエンド側のSDKから簡単に操作できる。フロントエンド側の実装だけで完結し、バックエンドを実装する必要がないのでハードルが低かった

デプロイ先

当初はFirebase Hostingを利用していました。Firestoreで既にFirebaseを利用していたためです。

その後、Vercelに乗り換えました。GitHubにPush後の自動デプロイが簡単に利用でき、無料で利用できるためです。

要件とその実現方法

自分だけがアクセスできる

人のデータを預かるのは、GDPR等を考えるとリスクになりかねないと考えました(あまり詳しくないですが)。また利用者を自分だけに制限した方が、破壊的変更も入れやすいです。

Firebase Authにおいては、登録自体を防ぐことはできないようでした。そのため、Firestoreのセキュリティルールによって自分以外のユーザーには何も操作ができないように制限していました。

利用するプラットフォームは無料かつクレジットカード登録が不要

無料で使えた方が気軽に開発できます。また無料枠が設定されていて超えると料金が発生してしまうようなプラットフォームは、失敗したときのリスクが大きいと考えました。趣味の開発でクラウド破産はしたくないですからね。

FirebaseもVercelも、クレジットカード登録不要で無料で使えます。

今後有料のプラットフォームを利用するとしても、従量課金ではなく定額のプランが提供されているものを選びたいと思っています。

感想

React

React開発もJavaScript開発も初めてという状態でした。学習しながら実装していくような形でした。ライブラリの選択としては正解だったと考えています。まさにEasyではなくSimpleを体現したフレームワークだと感じています。JSXという独自構文はあるものの薄めの糖衣構文とみなせるため、React特有で覚えなくてはならないことは最小限に抑えれています。

世界的にもシェアが大きいので、ライブラリも豊富です。また何かに困ったときにも検索すればすぐに情報が得られます。初心者はシェアの大きい方に乗っかっておくのが良いと感じました。

Firebase

認証からデータベースまですべて無料で簡単に使えるのは素晴らしいと思いました。

Firestoreについてもバックエンドの実装が不要でここまで動くものが作れるのはすごいと思います。スキーマレスなのでとにかく使い始めるまでが手軽です。ただしスキーマレスな点はデメリットでもあり、これが次のv2開発への動機につながっています。

v2

ソースコード

開発動機

Firestoreは実装が簡単なのですが、スキーマがないのでどんな型の値でも入ってしまいます。その結果としてTypeScriptとの相性が悪く、型付けを行うことが難しいという問題がありました。

さらに以前からRustを使ったアプリケーション開発に興味がありました。そのためフロントエンドとバックエンドを分離し、バックエンドはRustで書き直すことにしました。

技術スタック

フロントエンド

  • TypeScript
  • React
  • Vite
  • Mantine
  • urql
  • TanStack Table
    • Material Tabel → TanStack Table
  • Auth0
  • Vercel

バックエンド

  • Rust
    • actix-web
    • sqlx
    • async-graphql
  • GraphQL
  • PostgreSQL
  • Render
    • Heroku → Render
  • Supabase (データベースのホスティング先)
    • Heroku → Supabase

GraphQL

バックエンドはREST APIにするかGraphQLにするか検討しましたが、以下のような理由でGraphQLを選びました。

  • REST APIの様々な問題を解決しようとしている
  • GraphQLスキーマによって型が付く
  • コミュニティが活発でライブラリも豊富
  • 新しい技術を使ってみたかった

GraphQLはスキーマをもとに簡単にモックサーバーを作成することができます。これにより、Read-onlyのデモアプリケーションを公開することができました。

認証

認証はFirebase Authを使い続けても良かったのですが、以下のような理由でAuth0に乗り換えました。

バックエンドのデプロイ先

Rustを動かせるという時点でそこそこ選択肢が狭められてしまいます。

バックエンドのデプロイ先は、当初は以下のような理由でHerokuを選択しました。

  • Dockerで任意の言語のアプリケーションが動かせる
  • 無料でデプロイできる
  • 無料でPostgreSQLが提供される

その後Heroku有料化の影響を受け、別のプラットフォームを探しました。Renderが以下の条件を満たすようだったので、採用しました。

  • Dockerで任意の言語のアプリケーションが動かせる
  • 無料でデプロイできる

データベース

PostgreSQLを採用しました。理由としては以下のような点です。

Heroku有料化に伴い、移行先を探しました。現在はSupabaseを採用しています。Firebaseの代替を謳っているサービスですが、裏ではPostgreSQLが動いており、直接PostgreSQLに接続することもできます。

ちなみに他の選択肢としては、MySQLであればPlanetScaleという選択肢もあります。あまり調べていないですが、YugabyteDB ManagedもPostgreSQL互換で無料で使えるようです。

FirestoreからRDBに乗り換えたことで、多対多などの複雑なリレーションも表現しやすくなりました。

UIライブラリ

MUI

当初はシェアの大きいMUIを使っていました。情報も多いのでそれほど困ることはなかったのですが、一部微妙だと思うところもありました。

例えばフォームを作るとします。TextFieldであれば、ラベルからエラーメッセージまでわりと何でも面倒を見てくれます。ところがSelectになると話が変わってきます。エラーメッセージを表示しようとするとFormControlやFormHelperTextを使って自分で実装する必要が出てきます。その結果実装量が増えます。この上でReact Hook Formといった外部ライブラリとのつなぎ込みが入ってくるので、さらに大変なことになります。

Mantine

そこでMantineを採用してみました。こちらはReact Hook Formのようなフォームライブラリからnotistackのような通知システムまで自前で組み込んでいるUIライブラリです。使ってみるとかなり開発体験が良いです。さらにこちらに豊富なサンプルも提供されていて、フロントエンド初心者でもそれっぽい画面が作れるようになっています。

個人的には結構おすすめのコンポーネントライブラリです。

テーブルライブラリ

当初は@material-table/coreを使って実装していました。こちらはライブラリを読み込んでデータを渡すだけで、手軽に高機能な表が作れます。反面カスタマイズ性に乏しく、機能追加は難しいです。

その後TanStack Table v8に乗り換えました。こちらはヘッドレスのテーブルライブラリであり、テーブルに必要なロジック部分だけを提供していて、見た目部分はユーザー側で実装するというものです。実装量が増える分、柔軟にカスタマイズができます。

旧称React Table(もっとも今でもTanStack Tableの中のReact Tableという形で名前は消えていないようですが)と呼ばれていたv7の時点では、TypeScriptではない素のJavaScriptで実装されており、型があまりきれいに付かず使いづらい部分がありました。v8になってTypeScriptで書き直されたことで、かなり使いやすくなったと感じます。

TanStack Tableに乗り換えたことで、状態のURLへの永続化が可能になるなど、機能面でメリットの大きい変更でした。

バックエンドの設計

バックエンドサーバーでは、RustでClean Architectureを実践するという試みを実施してみました。そのあたりについてはこちらの記事にまとめたので、興味があればご覧ください。

https://zenn.dev/htlsne/articles/rust-clean-architecture

リライトの感想

v1の時点でPresentational ComponentとContainer Componentをある程度分離していたので、フロントエンド側の実装変更はそれほど大きくなく済みました。

バックエンドの実装には結構時間がかかりました。Rustは良い言語ですし、コミュニティも活発です。しかしWebアプリケーションを作る言語としてはまだ一般的とは言えず、ベストプラクティスもあまり固まっていない印象があります。Auth0のようなプラットフォームに対するSDKが提供されていないことも多いです。ですが決して不可能ではないですし、Rustでのプログラミングは面白いので挑戦する価値はあります。

DBについては、硬めに作るならスキーマレスのDBよりはRDBの方が向いていると思いました。私の中では、プロトタイプを高速に作るならスキーマレス、本格的に運用する堅牢なアプリケーションを作りたいならRDB、という結論になりました。

リライト後はかなり見通しが良くなりました。以前より機能追加もしやすくなったと感じます。

全体を通した感想

Webフロントエンドには苦手意識を感じていたのですが、以前よりこのあたりの技術に強くなったと感じます。副産物としてJavaScriptを書けるようになり、できることがかなり広がりました。1つWebアプリが作れると、2つ目以降はかなりハードルが低くなります。簡単なWebアプリケーションを作るというのは、自分にとってそれほどコストの高くない問題解決手段になりました。またSPA開発技術を応用して、ブラウザ拡張機能を作ることもできます。ブラウザの開発者コンソールでスクレイピングのようなことを行うこともできます。Node.jsはライブラリが豊富ですし、型がついたスクリプト言語としてはTypeScriptはかなり優秀です。そのため簡単なCLIツールを作るときにも使い勝手が良いです。

Rustでそれなりの規模のアプリケーションを作ってみる、という体験も面白かったです。以前からRustを実戦投入してみたかったので、技術的欲求を満たすことができました。

あとは誰かがどこかに書いていたことの二番煎じなのですが、開発にはとにかく時間が取られます。そのため開発のせいで本を読む時間がなく、結果として蔵書管理アプリを使う機会がない、といった本末転倒な事態になりかねません。シンプルなアプリケーションに見えても、実装してみるとかなりの手間がかかるものです。作りたいものがある人は、その辺のバランスについては考えておいた方が良いかもしれません。

今後について

まだ追加したい機能もありますし、リファクタリングが必要な箇所もあります。特にバックエンドについては、actix-webからaxumに移行してみたいと思っています。今後も気長に開発を続けていきたいと思います。

Discussion