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を取得します。 - リクエストデータで渡されたファイルをアップロードします。
- リクエストを行っているユーザーのプロフィールデータを新しい
profilePictureURLで更新します。 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ニュースレターに登録する