【制作したきっかけ】
日々の技術書やUdemyでの学習を管理し可視化するアプリを作成したいと思い作成しました。
【説明】
このアプリでは1週間の学習時間の目標設定、学習時間の記録と可視化、やることを整理するToDo管理機能、学習した内容をnotionでメモしたページを確認する機能があります。個人利用のみ対応しておりSign in を押すとgoogleの画面に遷移しますが私のgoogleアカウント以外ではサインインできません。
【機能】
ホーム画面
学習管理画面 (スクショの関係で正しく表示されていません)
私のgoogleアカウントでログインすると学習記録の追加や編集削除ボタンが表示されます。
学習記録作成モーダル
開始ボタンを押すと学習時間を計測するタイマーが開始されます。
タイマーはクライアント側のstateで管理されています。
ラベルも新規作成できます
タスク管理画面 (タスクを追加や編集画面はサインインしないと出ません)
カレンダー画面 (日ごとの学習記録が見れます)
学習記録編集
notionメモ一覧機能
学習した際にとったnotionのメモがカードとして一覧表示されています。
クリックするとnotionのリンクに飛びます。
最初はnotion APIから取得した内容をNext.jsのページで表示するようにしていたのですが
Vercelのホビープランではタイムアウトが30s固定となっておりnotionの記事を取得するとエラーが発生してしまいました。
わざわざnotionが提供している機能を車輪の最発明する必要はないと考えnotionのページに移動するように変更しました。
学習時間設定画面 (サインインしないと使えません)
【開発環境】
使用言語: typescript
使用フレームワーク: Next.js
使用ライブラリ: Auth.js
スタイリング: shadcn/ui tailwind css
CI/CD: github actions vercel
使用DB: MongoDB
開発環境: windows
無料でデプロイ可能なVercelにデプロイするため、またRSCを用いたServerActionsを体験してみたかったためNext.jsを採用しました。Next.jsのServerActionsやRSCを用いることによりNext.jsのみでフルスタックに開発ができました。基本的にはserver側で処理しuseStateやクライアント側でしか動作しないライブラリを使用するときにuse clientを用いました。
MongoDBを採用した理由は無料で使えるDBとしてMongoDBがあったことやNoSQLを使用してみたかったことがあげられます。しかし、振り返ってみると今回のアプリの仕様では個人利用を想定しているのでドキュメント指向DBでも対応可能でしたが後からほかのユーザーも使用するように変更を加える場合にテーブルの集計操作が多くなることが予想されるためNoSQLではなくほかのリレーショナルデータベースを扱っているSpabaseやNeonなどのDBaasを用いるべきだったと思います。
【開発期間】
2か月ほど
【工夫した点】
認証
認証にはNext.jsの認証ライブラリであるAuth.js (NextAuth)を採用しました。
認証を追加した理由は個人利用のためほかの人がCUD処理ができないように制御するためです。
Auth.jsを採用した理由としてはMongoDBとのアダプターがあることや今回Oauth認証を試してみたかったので採用しました。セッションはDB側に保存されており下の図のようなコレクションが作成され認証を管理します。(今回は登録機能がないためVerificationTokenはありません)
認証の流れとしてはGoogleのプロバイダーを使っているためサインインボタンを押すとgoogleの認可エンドポイントにリダイレクトされます。その後NextAuthがOatuhプロバイダーに対して認証リクエストを送信し認可コードを発行します。そして認可コードを用いてAuth.jsがアクセストークンを取得しアクセストークンを使用してユーザー情報を取得します。取得されたユーザー情報はDBに保存されセッションも保存されます。
クライアント側からセッションを取得する場合はuseSessionを用いて取得します。
トークンが期限切れの場合リフレッシュトークンを使用することで新しいアクセストークンを取得することができます。ログアウトするとDBのセッションが削除されます。
また、Next.jsのmiddlewareを用いてログインしていない場合はトップページにリダイレクトされます。
フロントエンド
スタイリングにはVercelが提供しておりRadix UIベースで作られたshadcn/uiを使用しています。Tailwind cssを用いて細かいスタイリングも調整できるのが魅力的です。
また、今回は勉強のためにjestとreact-testing-libraryを用いたコンポーネントの単体テストを作成しました。RSCの仕様を活用するためにuseFormState(useActionStateの古いバージョン)でのフォーム状態の管理をしました。
session情報を取得してユーザーがログインしていない場合はコンポーネントが表示されないようになっています。
バックエンド
バックエンドの処理はRoute Handlerをもちいてapiフォルダの中に記述します。
apiフォルダの中にバックエンドの記述をすることでVercelでデプロイするときにapiルート(サーバレス関数)としてデプロイされます。
CRUDのCRDなどのミューテーション処理はserver actionsを用いて実装しました。
バリデーションにはzodを用いています。
また、server Actionsの中やapiの処理もsession情報を取得し適切な権限がなければ実行されないようになっています。
CI
勉強のためにgithub actionsをもちいてPR作成時とmainにpush時に単体テストが実行されるCIを作成しました。E2Eテストの導入も検討しましたが、開発環境をdockerで管理していなかったのでテスト環境を作成するのが難しく断念しました。改めてコンテナの便利さや必要性を実感しました。
CD
CDはVercelと連携すると自動的に設定されます。ブランチにpushが発生するとvercel側でデプロイされます。
DB
DBにはMongoDBを使用しています。基本的には個人利用のアプリであるためドキュメント間に参照 (リレーション)は設定されていません。今回のアプリはACIDなどの強い制約が要求されるわけではないですが前述のとおりユーザーを登録してほかの人も使用するようにする場合は外部キーなどを設定し集計する必要があるためリレーショナルデータベースのほうが適しています。
【改善点】
ほかの人も利用できるようにしたかったのですがOauthのユーザー情報としてemailアドレスをDBに保存したり、noition連携をつけるとなるとpublic integrationを作成しnotionの情報などを取得しないといけなくそのような情報を管理するのはセキュリティに精通しているわけではない私がやるのは危険だと判断し、個人で利用できればよかったため断念しました。また、MongoDBの無料ストレージが512MBであることも要因の一つです。
単体テストのほかに後からE2Eテストも導入しようと考えていたのですがdockerで環境をコンテナ化できていなかったことや適切な設計ができていなかったため環境を整えることができませんでした。
【まとめ】
実際に開発を進めていき機能を追加していくうちに適切な設計の大切を実感しました。
コンポーネント同士が密結合だったり、コードが低凝集だったりすると後からどこにコードが記載されているのかわからなかったり仕様変更の際に苦労したりして反省しています。E2Eテストの導入もRepositoryパターンを用いてインターフェースに依存するようにすればテスト用DBに付け替えが容易に行えたなと思います。今後は後から拡張が容易な設計 フロントエンドだと(atomic design や container presentational pattern)バックエンドだとclean architectureなどを参考にしアプリケーションを開発していこうと思いました。
【デプロイ済みURL】
vecrlのホビープランがコールドスタートのため数秒は正しく表示されません。
https://progressnotes.vercel.app/