2022年4月28日

Remix、Prisma、MongoDBでフルスタックアプリケーションを構築する:参照整合性と画像アップロード

12分で読めます

MongoDB、Prisma、Remixを使用してフルスタックアプリケーションをゼロから構築する方法を学ぶこのシリーズの第4回記事へようこそ!今回は、画像アップロードコンポーネントを含むアプリケーションのプロファイル設定セクションを構築し、データに参照整合性を持たせるようにスキーマを設定します。

Build A Fullstack App with Remix, Prisma & MongoDB: Referential Integrity & Image Uploads

目次

はじめに

このシリーズの前のパートでは、称賛フィード、ユーザーリスト、最近の称賛リスト、称賛送信フォームなど、このアプリケーションの主要な領域を構築しました。

今回のパートでは、ユーザーが自分のプロフィール情報を更新し、プロフィール写真をアップロードする方法を構築することで、このアプリケーションの開発を締めくくります。また、データベースに参照整合性を持たせるために、スキーマにいくつかの変更を加えます。

:このプロジェクトの開始点は、GitHubリポジトリのpart-3ブランチにあります。このパートの最終結果を確認したい場合は、part-4ブランチに移動してください。

開発環境

提供されている例に従うには、以下が必要です...

:オプションの拡張機能は、TailwindとPrismaに非常に優れたインテリセンスと構文の強調表示を追加します。

プロファイル設定モーダルを構築する

アプリケーションのプロファイル設定ページは、ページの右上にあるプロファイル設定ボタンをクリックするとアクセスできるモーダルに表示されます。

app/components/search-bar.tsx

  • Prismaによって生成されたProfile型であるprofileという名前の新しいpropをエクスポートされたコンポーネントに追加します。
  • UserCircleコンポーネントをインポートします。
  • formの内容の最後にUserCircleコンポーネントをレンダリングし、新しいprofile propデータを渡します。これはプロファイル設定ボタンとして機能します。

開発サーバーがすでに実行されている場合、SearchBarコンポーネントがプロファイルデータを期待するようになったため、ホームページでエラーが発生します。

app/routes/home.tsxファイルで、このシリーズのパート2app/utils/auth.server.tsから記述されたgetUser関数を使用します。この関数を使用して、loader関数内でログインしているユーザーのデータをロードします。次に、そのデータをSearchBarコンポーネントに提供します。

SearchBarは、必要なprofileデータにアクセスできるようになります。以前にこのデータがないためにエラーが発生していた場合は、ブラウザのページをリフレッシュすると、ページの右上隅にプロファイルボタンが正常にレンダリングされていることが表示されるはずです。

モーダルを作成する

目標は、プロファイル設定ボタンをクリックしたときにプロファイル設定モーダルを開くことです。このシリーズの前のセクションで構築した称賛モーダルと同様に、新しいモーダルをレンダリングするネストされたルートを設定する必要があります。

app/routes/homeで、profile.tsxという名前の新しいファイルを作成し、開始するための次の内容を追加します

上記のコードスニペットは...

  • ... 新しいProfileSettingsコンポーネントにモーダルをレンダリングします。
  • ... loader関数内でログインしているユーザーのデータを取得して返します。
  • ... useLoaderDataフックを使用して、loader関数から返されたuserデータにアクセスします。

この新しいモーダルを開くには、app/components/search-bar.tsxで、UserCircleコンポーネントにonClickハンドラーを追加し、RemixのuseNavigateフックを使用してユーザーを/home/profileサブルートに移動させます。

プロファイル設定ボタンをクリックすると、新しいモーダルが画面に表示されるはずです。

フォームを構築する

構築するフォームには、ユーザーが自分のプロフィール詳細を変更できる3つのフィールドがあります。名前、苗字、部署です。

最初と最後の名前の入力を追加して、フォームの構築を開始します

上記に追加されたものの概要は次のとおりです

  1. 行われた変更に必要なインポートを追加しました。
  2. フォームの値を保持する状態のformDataオブジェクトを作成しました。これにより、これらの値はログインしているユーザーの既存のプロファイルデータにデフォルト設定されます。
  3. HTMLのchangeイベントとフィールド名をパラメータとして受け取る関数を作成しました。これらは、コンポーネントの入力フィールドの値が変更されると、formData状態を更新するために使用されます。
  4. フォームの基本的なレイアウトと2つの入力フィールドをレンダリングします。

現時点では、エラー処理は実装されておらず、フォームは何も実行しません。これらのピースを追加する前に、部署のドロップダウンを追加する必要があります。

app/utils/constants.tsで、Prismaスキーマで定義された可能なオプションを保持する新しいdepartments定数を追加します。そのファイルに次のエクスポートを追加します

departmentsapp/routes/home/profile.tsxファイルにSelectBoxコンポーネントとともにインポートし、それらを使用してフォームに新しい入力を追加します

この時点で、フォームは正しい入力とそのオプションをレンダリングする必要があります。それらの値は、ログインしているユーザーのプロファイルに関連付けられている現在の値にデフォルト設定されます。

ユーザーがフォームを送信できるようにする

次に構築するピースは、このフォームを機能させるaction関数です。

app/routes/home/profile.tsxで、requestオブジェクトからフォームデータを取得し、firstNamelastNamedepartmentフィールドを検証するaction関数を追加します

上記のaction関数は、次のことを行います

  1. requestオブジェクトから必要なフォームデータポイントを取り出します。
  2. 気にするデータの各ピースがstringデータ型であることを確認します。
  3. 以前に記述したvalidateName関数を使用してデータを検証します。
  4. 設定モーダルを閉じて、/homeルートにリダイレクトします。

上記のコードスニペットは、さまざまな検証が失敗した場合に関連するエラーもスローします。検証済みのデータを活用するには、ユーザーを更新できる関数を作成します。

app/utils/user.server.tsで、次の関数をエクスポートします

この関数を使用すると、任意のprofileデータを渡して、idが指定されたuserIdと一致するユーザーを更新できます。

app/routes/home/profile.tsxファイルに戻り、その新しい関数をインポートし、action関数内でログインしているユーザーを更新するために使用します

ユーザーが保存ボタンを押すと、更新されたプロファイルデータが保存され、モーダルが閉じられます。

画像アップロードコンポーネントを追加する

AWSアカウントを設定する

ユーザーはプロファイルの主要な情報を更新できるようになりましたが、追加すると便利なことの1つは、ユーザーがプロフィール写真を設定できるようにして、他のユーザーがより簡単に識別できるようにすることです。

これを行うには、アップロードされた画像を保持するためのAWS S3ファイルストレージバケットを設定します。AWSアカウントをまだお持ちでない場合は、こちらからサインアップできます。

:Amazonは、S3への無料アクセスを提供する無料利用枠を提供しています。

IAMユーザーを作成する

アカウントを取得したら、Identity Access Management(IAM)ユーザーをAWSで設定する必要があります。これにより、S3と対話するために必要なアクセスキーIDシークレットキーを生成できます。

:IAMユーザーとそのキーを既にお持ちの場合は、先に進んでください。

AWSコンソールホームページに移動します。ページの右上隅にあるユーザー名でラベル付けされたドロップダウンをクリックし、セキュリティ認証情報を選択します。

そのセクションに入ったら、左側のメニューのアクセス管理の下にあるユーザーオプションをクリックします。

このページで、ページの右上にあるユーザーを追加ボタンをクリックします。

これにより、ユーザーを構成できる短いウィザードが表示されます。以下の手順に従ってください

最初のセクションでは、次を求められます

  1. ユーザー名:任意のユーザー名を指定します。
  2. AWSアクセスタイプを選択アクセスキーIDシークレットキーの生成を有効にするアクセスキー - プログラムによるアクセスオプションを選択します。

ウィザードの2番目のステップで、次の選択を行います

  1. 「既存のポリシーを直接アタッチする」オプションを選択します。
  2. 「S3」という用語を検索します。
  3. AmazonS3FullAccessというラベルの付いたオプションの横にあるチェックマークをオンにします。
  4. フォームの下部にある[次へ]をクリックします。

アカウントのユーザーの管理と整理を容易にするためにユーザーにタグを追加する場合は、ウィザードの3番目のステップでここに追加します。このページで完了したら、次へをクリックします。

このページの概要が問題なければ、ページの下部にあるユーザーを作成ボタンをクリックします。

そのボタンをクリックすると、アクセスキーIDシークレットキーが表示されたページに移動します。これらはすぐに使用するため、コピーして簡単にアクセスできる場所に保存してください。

S3バケットを設定する

ユーザーとアクセスキーができたので、AWS S3ダッシュボードに移動し、ファイルストレージバケットを設定します。

このページの右上にあるバケットを作成ボタンをクリックします。

バケットの名前とリージョンを求められます。これらの詳細を入力し、以前に保存したアクセスキーIDシークレットキーを使用して選択した値を保存します。これらも後で必要になります。

入力したら、フォームの一番下にあるバケットを作成をクリックします。

バケットの作成が完了すると、オブジェクトタブのバケットのダッシュボードページに送信されます。アクセス許可タブに移動します。

このタブで、パブリックアクセスをブロックセクションの下にある編集ボタンをクリックします。このフォームで、すべてのパブリックアクセスをブロックボックスをオフにし、変更を保存をクリックします。これにより、バケットがパブリックとして設定され、アプリケーションが画像にアクセスできるようになります。

そのセクションの下に、バケットポリシーセクションが表示されます。次のポリシーを貼り付け、必ず<bucket-name>をバケットの名前に置き換えてください。このポリシーにより、画像が公開読み取り可能になります

AWSユーザーとS3バケットが設定されました。次に、キーとバケット構成を.envファイルに保存して、後で使用できるようにする必要があります。

Prismaスキーマを更新する

アップロードされた画像へのリンクを保存するフィールドをデータベースに作成します。これらはProfile埋め込みドキュメントとともに保存する必要があります。したがって、Profile型ブロックに新しいフィールドを追加します。

Prisma Clientをこれらの変更で更新するには、npx prisma generateを実行します。

画像アップロードコンポーネントを構築する

app/componentsimage-uploader.tsxという名前の新しいファイルを作成し、次の内容を追加します

上記のコードスニペットは、完全な画像アップロードコンポーネントです。以下は、何が起こっているかの概要です

  1. preventDefault関数は、コンポーネントのファイル入力への変更を処理するために定義されています。
  2. handleDrop関数は、コンポーネントのファイル入力でのdropイベントを処理するために定義されています。
  3. handleChange関数は、コンポーネントのファイル入力でのchangeイベントを処理するために定義されています。
  4. divは、さまざまなイベントハンドラーが定義されてレンダリングされ、ファイルのドロップ、ドラッグイベント、クリックに反応できます。これらは、画像アップロードをトリガーし、要素がドラッグイベントを受信している場合にのみ表示されるスタイルの変更に使用されます。

このコンポーネントのinputの値が変更されるたびに、propsonChange関数が呼び出され、ファイルデータが渡されます。そのデータがS3にアップロードされるものです。

次に、画像アップロードを処理するサービスを作成します。

画像アップロードサービスを構築する

画像アップロードサービスを構築するには、2つの新しいnpmパッケージが必要です

  • aws-sdk:AWSサービスと対話できるJavaScript APIを公開します。
  • cuid:一意のIDを生成するために使用されるツール。これを使用してランダムなファイル名を生成します。

画像アップロードサービスは、新しいユーティリティファイルに配置されます。app/utilss3.server.tsという名前のファイルを作成します。

アップロードを処理するために、Remixのunstable_parseMultipartFormData関数を使用します。この関数は、requestオブジェクトのmultipart/form-data値を処理します。

multipart/form-dataは、フォーム全体にファイルを投稿する場合のフォームデータタイプです。

unstable_parseMultipartFormDataは、2つのパラメータを受け取ります

  1. フォーム送信から取得されたrequestオブジェクト。
  2. uploadHandler関数。これは、ファイルデータをストリーミングし、アップロードを処理します。

unstable_parseMultipartFormData関数は、以前に使用したRemixのrequest.formData関数と同様の方法で使用されます。

作成した新しいファイルに、次の関数とインポートを追加します

このコードは、バケットと対話できるようにS3 APIを設定します。また、uploadHandler関数も追加します。この関数は

  1. AWSユーザーとS3バケットを設定するときに保存した環境変数を使用して、S3 SDKを設定します。
  2. データキーの名前が'profile-pic'である限り、requestからファイルデータをストリーミングします。
  3. ファイルをS3にアップロードします。
  4. S3が返すLocationデータを返します。これには、S3の新しいファイルのURLロケーションが含まれます。

uploadHandlerが完了したので、requestオブジェクトを実際に入力として受け取り、uploadHandlerとともにunstable_parseMultipartFormData関数に渡す別の関数を追加します。

この関数にはrequestオブジェクトが渡されます。これは、後でaction関数から送信されます。

ファイルデータはuploadHandler関数を介して渡されます。この関数は、S3へのアップロードを処理し、formDataは、新しいファイルのロケーションをフォームデータオブジェクト内で返します。'profile-pic' URLは、そのオブジェクトからプルされ、関数によって返されます。

コンポーネントとサービスを活用する

これで、プロファイル写真のアップロードを機能させるために必要な2つのピースが完成しました。それらをまとめます。

アップロードフォームデータを処理するリソースルートを、app/routesavatar.tsという名前の新しいファイルを作成し、次のaction関数を追加して作成します

上記の関数は、アップロードフォームを処理するために次の手順を実行します

  1. リクエストユーザーのidを取得します。
  2. リクエストデータで渡されたファイルをアップロードします。
  3. リクエストユーザーのプロファイルデータを新しいprofilePicture URLで更新します。
  4. imageUrl変数でPOSTリクエストに応答します。

これで、ImageUploaderコンポーネントを使用してファイルアップロードを処理し、ファイルデータをこの新しい/avatarルートに送信できます。

app/routes/home/profile.tsxで、ImageUploaderコンポーネントをインポートし、入力フィールドの左側のフォームに追加します。

また、ImageUploaderコンポーネントによって発行されたonChangeイベントを処理する新しい関数と、プロファイル写真データを保存するためのformData変数に新しいフィールドを追加します。

フォームに移動してファイルをアップロードしようとすると、データはS3、データベース、およびフォームの状態に正しく保存されるはずです。

プロフィール画像を表示する

素晴らしい!画像アップロードはスムーズに動作しています。ユーザーのサークルが表示されるサイト全体にこれらの画像を表示するだけです。

app/components/user-circle.tsxUserCircleコンポーネントを開き、これらの変更を行って、サークルの背景画像をプロフィール写真が利用可能な場合はプロフィール写真になるように設定します

これで、数人のユーザーにプロフィール写真を提供すると、サイト全体に表示されるはずです!

アカウント削除機能を追加する

プロファイル設定モーダルに必要な最後の機能は、アカウントを削除する機能です。

特にスキーマレスデータベースでデータを削除すると、「孤立ドキュメント」、つまり、かつて親ドキュメントに関連付けられていたが、親が途中で削除されたドキュメントが作成される可能性があります。

このセクションでは、そのシナリオに対するセーフガードを導入します。

削除ボタンを追加する

このフォームは、サインインフォームとサインアップフォームの処理方法と同様の方法で処理します。この1つのフォームは、action関数に受信するリクエストの種類を知らせる_actionキーを送信します。

app/routes/home/profile.tsxで、ProfileSettings関数で返されたformに次の変更を加えます

これで、クリックされたボタンに応じて、action関数で異なる_actionを処理できます。

action関数を更新して、switchステートメントを使用してさまざまなアクションを実行するようにします

ユーザーがフォームを保存すると、'save'ケースがヒットし、既存の機能が発生します。'delete'ケースは現在何も実行しません。

app/utils/user.server.tsに、idを受け取り、それに関連付けられたユーザーを削除する新しい関数を追加します

これで、プロファイルページの"delete"ケースの残りの部分を埋めることができます。

ユーザーはアカウントを削除できるようになりました!

データモデルを更新して参照整合性を追加する

このユーザー削除機能の唯一の問題は、ユーザーが削除されると、そのユーザーが作成したすべての称賛が孤立することです。

参照アクションを使用して、作成者が削除されたときに称賛の削除をトリガーできます。

npx prisma db pushを実行して、これらの変更を伝播し、Prisma Clientを生成します。

これで、アカウントを削除すると、そのアカウントによって作成されたKudosもアカウントとともに削除されます!

フォームのバリデーションを追加する

終わりに近づいてきました!最後のピースは、プロファイル設定フォームでエラーメッセージ処理をフックアップすることです。

action関数はすでにすべての正しいエラーメッセージを返しています。それらを処理するだけで済みます。

app/routes/home/profile.tsxで次の変更を行って、これらのエラーを処理します

上記のコードスニペットでは、次の変更が行われました

  1. useActionDataフックを使用して、エラーメッセージを取得しました。これらは状態変数に保存され、ユーザーが不正なフォームを送信した後にモーダルに戻された場合にフォームに入力するために使用されました。
  2. フォームレベルのエラーを表示するために、エラー出力が追加されました。
  3. エラーデータは、必要に応じてフィールドレベルのエラーを表示できるように、FormFieldコンポーネントに渡されました。

上記の変更を加えると、フォームと検証のエラーがフォームに表示されます。

まとめと今後の展望

この記事で行われた変更により、称賛アプリケーションを無事に完成させました!サイトのすべてのピースが機能し、ユーザーに出荷する準備ができています。

このセクションでは、以下について学びました

  • Remixのネストされたルート
  • AWS S3
  • PrismaとMongoDBを使用した参照アクションと整合性

このシリーズの次のセクションでは、構築したアプリケーションを取得してVercelにデプロイすることで、物事をまとめます!

次回の投稿をお見逃しなく!

Prismaニュースレターにサインアップ