MongoDB、Prisma、Remix を使用してフルスタックアプリケーションをゼロから構築する方法を学ぶこのシリーズの第4弾へようこそ!このパートでは、画像アップロードコンポーネントを含むアプリケーションのプロファイル設定セクションを構築し、データの参照整合性を提供するためにスキーマを構成します。
目次
- はじめに
- プロファイル設定モーダルを構築する
- 画像アップロードコンポーネントを追加する
- プロフィール写真を表示する
- アカウント削除機能を追加する
- フォームバリデーションを追加する
- まとめ & 次のステップ
はじめに
このシリーズの前回のパートでは、Kudosフィード、ユーザーリスト、最近のKudosリスト、Kudos送信フォームなど、このアプリケーションの主要な領域を構築しました。
このパートでは、ユーザーがプロフィール情報を更新し、プロフィール写真をアップロードする方法を構築することで、このアプリケーションの開発を締めくくります。また、データベースに参照整合性を持たせるために、スキーマにいくつかの変更を加えます。
注: このプロジェクトの開始点は、GitHubリポジトリのpart-3ブランチで入手できます。このパートの最終結果を確認したい場合は、part-4ブランチをご覧ください。
開発環境
提供された例に従うには、次のことが必要です...
- ... Node.jsがインストールされていること。
- ... Gitがインストールされていること。
- ... TailwindCSS VSCode Extensionがインストールされていること。(任意)
- ... Prisma VSCode Extensionがインストールされていること。(任意)
注: オプションの拡張機能は、TailwindとPrismaに非常に優れたIntelliSenseと構文ハイライトを追加します。
プロファイル設定モーダルを構築する
アプリケーションのプロファイル設定ページは、ページ右上のプロファイル設定ボタンをクリックするとアクセスできるモーダルで表示されます。
app/components/search-bar.tsx
にて
- エクスポートされたコンポーネントに、Prismaによって生成された
Profile
型を持つprofile
という新しいプロパティを追加します。 UserCircle
コンポーネントをインポートします。form
の内容の最後にUserCircle
コンポーネントをレンダリングし、新しいprofile
プロパティデータを渡します。これはプロファイル設定ボタンとして機能します。
開発サーバーがすでに実行されている場合、これは、SearchBar
コンポーネントがプロファイルデータを予期するようになったため、ホームページでエラーが発生します。
app/routes/home.tsx
ファイルで、このシリーズの第2部で作成したapp/utils/auth.server.ts
のgetUser
関数を使用します。この関数を使用して、ログインしたユーザーのデータをloader
関数内にロードします。次に、そのデータをSearchBar
コンポーネントに提供します。
SearchBar
は、必要なprofile
データにアクセスできるようになりました。以前にこのデータがないためにエラーが発生した場合は、ブラウザでページを更新すると、ページの右上隅にプロファイルボタンが正常にレンダリングされていることがわかります。
モーダルを作成する
目標は、プロフィール設定ボタンがクリックされたときにプロフィール設定モーダルを開くことです。このシリーズの前回のセクションで構築したkudosモーダルと同様に、新しいモーダルをレンダリングするネストされたルートを設定する必要があります。
app/routes/home
に、以下の内容のprofile.tsx
という新しいファイルを追加して開始します。
上記のコードスニペットは...
- ... 新しい
ProfileSettings
コンポーネントにモーダルをレンダリングします。 - ...
loader
関数内でログインユーザーのデータを取得し、返します。 - ...
useLoaderData
フックを使用して、loader
関数から返されたuser
データにアクセスします。
この新しいモーダルを開くには、app/components/search-bar.tsx
で、UserCircle
コンポーネントにonClick
ハンドラーを追加し、RemixのuseNavigate
フックを使用してユーザーを/home/profile
サブルートに移動させます。
これでプロフィール設定ボタンをクリックすると、新しいモーダルが画面に表示されるはずです。
フォームを構築する
構築するフォームには、ユーザーがプロフィールの詳細(名、姓、部署)を変更できる3つのフィールドがあります。
最初と最後の名前の入力を追加してフォームの構築を開始します
ここには、上記で追加されたものの概要があります。
- 変更に必要なインポートを追加しました。
- フォームの値を保持する
formData
オブジェクトを状態に作成しました。これは、ログインユーザーの既存のプロファイルデータにそれらの値をデフォルト設定します。 - HTML
change
イベントとフィールド名をパラメータとして受け取る関数を作成しました。これらは、コンポーネントの入力フィールドの値が変更されるときにformData
の状態を更新するために使用されます。 - フォームの基本的なレイアウトと、2つの入力フィールドをレンダリングします。
この時点では、エラー処理は設定されておらず、フォームは何も行いません。それらの部分を追加する前に、部署のドロップダウンを追加する必要があります。
app/utils/constants.ts
に、Prismaスキーマで定義されている可能なオプションを保持するための新しいdepartments
定数を追加します。そのファイルに以下のエクスポートを追加します
departments
をapp/routes/home/profile.tsx
ファイルにSelectBox
コンポーネントとともにインポートし、それらを使用してフォームに新しい入力を追加します
この時点で、フォームは正しい入力とそのオプションをレンダリングするはずです。デフォルト値は、ログイン中のユーザーのプロフィールに関連付けられている現在の値になります。
ユーザーがフォームを送信できるようにする
次に構築する部分は、このフォームを機能させるaction
関数です。
app/routes/home/profile.tsx
ファイルに、request
オブジェクトからフォームデータを取得し、firstName
、lastName
、department
フィールドを検証するaction
関数を追加します。
上記のaction
関数は次のことを行います。
request
オブジェクトから必要なフォームデータポイントを抽出します。- 関心のある各データが
string
データ型であることを確認します。 - 以前に作成した
validateName
関数を使用してデータを検証します。 /home
ルートにリダイレクトし、設定モーダルを閉じます。
上記のコードスニペットは、さまざまな検証が失敗した場合に関連するエラーもスローします。検証済みのデータを活用するために、ユーザーを更新する関数を作成します。
app/utils/user.server.ts
で、以下の関数をエクスポートします。
この関数を使用すると、任意のprofile
データを渡して、提供されたuserId
とid
が一致するユーザーを更新できます。
app/routes/home/profile.tsx
ファイルに戻り、その新しい関数をインポートし、それを使用してaction
関数内でログインしたユーザーを更新します。
これでユーザーが保存ボタンを押すと、更新されたプロファイルデータが保存され、モーダルが閉じられます。
画像アップロードコンポーネントを追加する
AWSアカウントを設定する
ユーザーはプロフィールの重要な情報を更新できるようになりましたが、さらに追加したいのは、他のユーザーがより簡単に識別できるように、ユーザーがプロフィール写真を設定できる機能です。
これを行うには、アップロードされた画像を保存するためのAWS S3ファイルストレージバケットを設定します。AWSアカウントをまだお持ちでない場合は、こちらからサインアップできます。
注: Amazonは、S3を無料で利用できる無料枠を提供しています。
IAMユーザーを作成する
アカウントを作成したら、AWSでIdentity Access Management (IAM) ユーザーを設定する必要があります。これにより、S3とやり取りするために必要なアクセスキーIDとシークレットキーの両方を生成できます。
注: 既にIAMユーザーとそのキーをお持ちの場合は、読み飛ばしてください。
AWSコンソールのホームぺージに移動してください。ページの右上隅にあるユーザー名のラベルが付いたドロップダウンをクリックし、Security Credentialsを選択してください。
そのセクションに入ったら、左側のメニューのアクセス管理の下にあるユーザーオプションをクリックします。
このページで、ページの右上にあるユーザーを追加ボタンをクリックします。
これにより、ユーザーを構成できる短いウィザードが表示されます。以下の手順に従ってください
この最初のセクションでは、以下を求められます。
- ユーザー名: 任意のユーザー名を入力してください。
- AWSアクセスタイプを選択: アクセスキー - プログラムによるアクセスオプションを選択します。これにより、アクセスキーIDとシークレットキーの生成が可能になります。
ウィザードの2番目のステップで、次の選択を行います。
- 「既存のポリシーを直接アタッチ」オプションを選択します。
- "S3"という用語を検索します。
- AmazonS3FullAccessというラベルのオプションの横にあるチェックマークを入れます。
- フォーム下部の「次へ」をクリックします。
アカウント内のユーザーをより管理しやすく、整理しやすくするためにユーザーにタグを追加したい場合は、ウィザードの3番目のステップでここに追加してください。このページでの作業が完了したら、次へをクリックしてください。
このページの概要に問題がなければ、ページ下部のユーザーを作成ボタンをクリックしてください。
そのボタンを押すと、アクセスキーIDとシークレットキーが表示されたページに移動します。それらをコピーして、すぐに使用するのでアクセスしやすい場所に保存してください。
S3バケットを設定する
ユーザーとアクセスキーが用意できたので、ファイルストレージバケットを設定するAWS S3ダッシュボードに移動します。
このページの右上にあるバケットを作成ボタンをクリックします。
バケットの名前とリージョンを求められます。それらの詳細を入力し、選択した値を以前保存したアクセスキーIDとシークレットキーとともに保存します。これらも後で必要になります。
それらを入力したら、フォームの一番下にあるバケットを作成をクリックします。
バケットの作成が完了すると、バケットのダッシュボードページの「オブジェクト」タブに移動します。「アクセス許可」タブに移動します。
このタブで、「パブリックアクセスをブロック」セクションの下にある「編集」ボタンをクリックします。このフォームで、「すべてのパブリックアクセスをブロック」のチェックを外し、「変更を保存」をクリックします。これにより、バケットがパブリックに設定され、アプリケーションが画像にアクセスできるようになります。
そのセクションの下にバケットポリシーセクションが表示されます。以下のポリシーを貼り付け、<bucket-name>
をあなたのバケット名に必ず置き換えてください。このポリシーにより、画像がパブリックに読み取り可能になります。
これでAWSユーザーとS3バケットの設定が完了しました。次に、キーとバケットの設定を.env
ファイルに保存し、後で利用できるようにする必要があります。
Prismaスキーマを更新する
アップロードされた画像へのリンクを保存するフィールドをデータベースに作成します。これらはProfile
埋め込みドキュメントに保存されるべきなので、Profile
型ブロックに新しいフィールドを追加します。
これらの変更でPrisma Clientを更新するには、npx prisma generate
を実行します。
画像アップロードコンポーネントを構築する
app/components
にimage-uploader.tsx
という新しいファイルを以下の内容で作成します。
上記のコードスニペットは、完全な画像アップロードコンポーネントです。何が起こっているかの概要は次のとおりです。
- コンポーネントのファイル入力への変更を処理するために、
preventDefault
関数が定義されています。 - コンポーネントのファイル入力での
drop
イベントを処理するために、handleDrop
関数が定義されています。 - コンポーネントのファイル入力での
change
イベントを処理するために、handleChange
関数が定義されています。 div
は、さまざまなイベントハンドラーが定義されてレンダリングされ、ファイルドロップ、ドラッグイベント、クリックに反応できるようになっています。これらは、画像アップロードと、要素がドラッグイベントを受信したときにのみ表示されるスタイル変更をトリガーするために使用されます。
このコンポーネントのinput
の値が変更されるたびに、props
からのonChange
関数が呼び出され、ファイルデータが渡されます。このデータがS3にアップロードされます。
次に、画像アップロードを処理するサービスを作成します。
画像アップロードサービスを構築する
画像アップロードサービスを構築するには、2つの新しいnpmパッケージが必要です。
画像アップロードサービスは、新しいユーティリティファイルに存在します。app/utils
にs3.server.ts
というファイルを作成します。
アップロードを処理するために、Remixのunstable_parseMultipartFormData
関数を使用します。これは、request
オブジェクトのmultipart/form-data
値を処理します。
注:
multipart/form-data
は、フォーム内でファイル全体を投稿する際のフォームデータタイプです。
unstable_parseMultipartFormData
は2つのパラメータを取ります。
- フォーム送信から取得した
request
オブジェクト。 - ファイルデータをストリーミングし、アップロードを処理する
uploadHandler
関数。
注:
unstable_parseMultipartFormData
関数は、これまで使用してきたRemixのrequest.formData
関数と同様の方法で使用されます。
作成した新しいファイルに以下の関数とインポートを追加します。
このコードはS3 APIを設定し、バケットと対話できるようにします。また、uploadHandler
関数も追加します。この関数は、次のことを行います。
- AWSユーザーとS3バケットの設定時に保存した環境変数を使用して、S3 SDKを設定します。
- データキー名が
'profile-pic'
である限り、request
からファイルデータをストリーミングします。 - ファイルをS3にアップロードします。
- S3が返す
Location
データを返します。これには、新しいファイルのS3におけるURL位置が含まれます。
uploadHandler
が完了したので、request
オブジェクトを受け取り、それをuploadHandler
とともにunstable_parseMultipartFormData
関数に渡す別の関数を追加します。
この関数にはrequest
オブジェクトが渡され、後でaction
関数から送信されます。
ファイルデータはuploadHandler
関数を介して渡され、S3へのアップロードを処理します。formData
は、フォームデータオブジェクト内の新しいファイルの場所を返します。'profile-pic'
のURLは、そのオブジェクトから抽出され、関数によって返されます。
コンポーネントとサービスを使用する
これで、プロファイル画像のアップロードを実装するために必要な2つのピースが完成しましたので、それらを組み合わせます。
app/routes
にavatar.ts
という新しいファイルを以下のaction
関数で作成し、アップロードフォームデータを処理するリソースルートを追加します。
上記の関数は、アップロードフォームを処理するために以下の手順を実行します。
- リクエストを行っているユーザーの
id
を取得します。 - リクエストデータで渡されたファイルをアップロードします。
- リクエストを行っているユーザーのプロフィールデータを新しい
profilePicture
URLで更新します。 POST
リクエストにimageUrl
変数を応答します。
これで、ImageUploader
コンポーネントを使用してファイルアップロードを処理し、ファイルデータをこの新しい/avatar
ルートに送信できます。
app/routes/home/profile.tsx
で、ImageUploader
コンポーネントをインポートし、入力フィールドの左側にフォームに追加します。
また、ImageUploader
コンポーネントから発行されたonChange
イベントを処理する新しい関数と、プロフィール写真データを格納するためのformData
変数に新しいフィールドを追加します。
このフォームにアクセスしてファイルをアップロードしようとすると、データはS3、データベース、およびフォームの状態に正しく保存されるはずです。
プロフィール写真を表示する
これは素晴らしい!画像アップロードがスムーズに機能しています。あとは、ユーザーのサークルが表示されるサイト全体でそれらの画像を表示するだけです。
app/components/user-circle.tsx
にあるUserCircle
コンポーネントを開き、利用可能な場合はサークルの背景画像をプロフィール写真に設定するために以下の変更を行います。
これで、数人のユーザーにプロフィール写真を設定すると、サイト全体に表示されるはずです!
アカウント削除機能を追加する
プロフィール設定モーダルに必要な最後の機能は、アカウントを削除する機能です。
特にスキーマレスデータベースにおいてデータを削除すると、かつて親ドキュメントに関連付けられていたドキュメントが、親が削除されたために「孤立ドキュメント」となる可能性があります。
このセクションでは、そのシナリオに対するセーフガードを設けます。
削除ボタンを追加する
このフォームは、サインインフォームやサインアップフォームと同様の方法で処理します。このフォームは、action
関数にどのような種類のリクエストを受け取ったかを知らせる_action
キーを送信します。
app/routes/home/profile.tsx
で、ProfileSettings
関数で返されるform
に以下の変更を加えます。
これで、クリックされたボタンに応じて、action
関数で異なる_action
を処理できるようになります。
action
関数を更新して、switch
ステートメントを使用して異なるアクションを実行するようにします。
これで、ユーザーがフォームを保存すると、'save'
ケースが実行され、既存の機能が動作します。ただし、'delete'
ケースは現在何も行いません。
app/utils/user.server.ts
に、id
を受け取り、それに関連付けられたユーザーを削除する新しい関数を追加します。
これで、プロフィールページの"delete"
ケースの残りの部分を記述できます。
ユーザーは自分のアカウントを削除できるようになりました!
参照整合性を追加するためにデータモデルを更新する
このユーザー削除機能の唯一の問題は、ユーザーが削除されると、そのユーザーが作成したKudosがすべて孤立してしまうことです。
参照アクションを使用して、Kudosの作成者が削除されたときに、Kudosの削除をトリガーできます。
npx prisma db push
を実行して、これらの変更を反映させ、Prisma Clientを生成します。
これで、アカウントを削除すると、そのアカウントが作成したKudos
も一緒に削除されます!
フォームバリデーションを追加する
もうすぐ終わりです!最後の部分は、プロファイル設定フォームでのエラーメッセージ処理を連携させることです。
action
関数はすでにすべての正しいエラーメッセージを返しています。それらを処理するだけで済みます。
これらのエラーを処理するために、app/routes/home/profile.tsx
で以下の変更を行います。
上記のコードスニペットでは、以下の変更が行われました。
useActionData
フックを使用してエラーメッセージを取得しました。それらは状態変数に格納され、ユーザーが不正なフォームを送信した後でモーダルに戻された場合にフォームにデータを入力するために使用されました。- フォームレベルのエラーを表示するためのエラー出力が追加されました。
- エラーデータは、必要に応じてフィールドレベルのエラーを表示できるように、
FormField
コンポーネントに渡されました。
上記の変更を行った後、フォームと検証エラーがフォームに表示されるようになります。
まとめ & 次のステップ
この記事で行われた変更により、Kudosアプリケーションは無事完成しました!サイトのすべての部分が機能し、ユーザーに提供する準備が整いました。
このセクションでは、以下のことを学びました。
- Remixのネストされたルート
- AWS S3
- PrismaとMongoDBによる参照アクションと整合性
このシリーズの次のセクションでは、構築したアプリケーションをVercelにデプロイして締めくくります!
次の投稿をお見逃しなく!
Prismaニュースレターに登録する