1. 始める前に
この Codelab では、Firebase Data Connect を Cloud SQL データベースと統合して、映画レビュー ウェブアプリを構築します。完成したアプリは、Firebase Data Connect が SQL を活用したアプリケーションの構築プロセスを簡素化する方法を示しています。主な機能は次のとおりです。
- 認証: アプリのクエリとミューテーションにカスタム認証を実装し、承認されたユーザーのみがデータにアクセスできるようにします。
- GraphQL スキーマ: 映画レビュー ウェブアプリのニーズに合わせて柔軟な GraphQL スキーマを使用して、データ構造を作成、管理します。
- SQL クエリとミューテーション: GraphQL を活用したクエリとミューテーションを使用して、Cloud SQL でデータを取得、更新、管理します。
- 部分一致による詳細検索: フィルタと検索オプションを使用して、タイトル、説明、タグなどのフィールドに基づいて映画を検索します。
- (省略可) ベクター検索の統合: Firebase Data Connect のベクター検索を使用してコンテンツ検索機能を追加し、入力と設定に基づいて豊富なユーザー エクスペリエンスを提供します。
前提条件
JavaScript の基本的な知識が必要です。
ラボの内容
- ローカル エミュレータを使用して Firebase Data Connect を設定する。
- Data Connect と GraphQL を使用してデータスキーマを設計する。
- 映画レビュー アプリ用のさまざまなクエリとミューテーションを作成してテストします。
- Firebase Data Connect がアプリで SDK を生成し、使用する仕組みについて学びます。
- スキーマをデプロイして、データベースを効率的に管理します。
必要なもの
- Git
- Visual Studio Code
- nvm-windows(Windows)または nvm(macOS/Linux)を使用して Node.js をインストールします。
- まだ作成していない場合は、Firebase コンソールで Firebase プロジェクトを作成します。
- (省略可)ベクトル検索の場合は、プロジェクトを従量課金制の Blaze のお支払いプランにアップグレードします。
2. 開発環境を設定する
この Codelab のステージでは、Firebase Data Connect を使用して映画レビュー アプリの作成を開始するための環境を設定します。
- プロジェクト リポジトリのクローンを作成し、必要な依存関係をインストールします。
git clone https://github.com/firebaseextended/codelab-dataconnect-web cd codelab-dataconnect-web cd ./app && npm i npm run dev
- これらのコマンドを実行したら、ブラウザで http://localhost:5173 を開き、ローカルで実行されているウェブアプリを確認します。これは、映画レビュー アプリを構築し、その機能を操作するためのフロントエンドとして機能します。
- Visual Studio Code を使用して、クローンを作成した
codelab-dataconnect-web
フォルダを開きます。ここでは、スキーマの定義、クエリの作成、アプリの機能のテストを行います。 - Data Connect の機能を使用するには、Firebase Data Connect Visual Studio 拡張機能をインストールします。
または、Visual Studio Code Marketplace から拡張機能をインストールするか、VS Code 内で拡張機能を検索します。 - Firebase コンソールで Firebase プロジェクトを開くか、新しい Firebase プロジェクトを作成します。
- Firebase プロジェクトを Firebase Data Connect VSCode 拡張機能に接続します。拡張機能で、次の操作を行います。
- [ログイン] ボタンをクリックします。
- [Firebase プロジェクトを接続] をクリックして、Firebase プロジェクトを選択します。
- Firebase Data Connect VS Code 拡張機能を使用して Firebase エミュレータを起動します。
[エミュレータを起動] をクリックし、エミュレータがターミナルで実行されていることを確認します。
3. スターター コードベースを確認する
このセクションでは、アプリのスターター コードベースの主要な部分について説明します。このアプリには一部の機能が欠落していますが、全体的な構造を理解するのに役立ちます。
フォルダとファイルの構造
次のサブセクションでは、アプリのフォルダとファイル構造の概要について説明します。
dataconnect/
ディレクトリ
Firebase Data Connect の構成、コネクタ(クエリとミューテーションを定義)、スキーマ ファイルが含まれています。
schema/schema.gql
: GraphQL スキーマを定義します。connector/queries.gql
: アプリで必要なクエリconnector/mutations.gql
: アプリで必要なミューテーションconnector/connector.yaml
: SDK 生成の構成ファイル
app/src/
ディレクトリ
アプリケーション ロジックと Firebase Data Connect とのやり取りが含まれます。
firebase.ts
: Firebase プロジェクト内の Firebase アプリに接続するための構成。lib/dataconnect-sdk/
: 生成された SDK が含まれています。SDK 生成の場所はconnector/connector.yaml
ファイルで編集できます。クエリまたはミューテーションを定義するたびに SDK が自動的に生成されます。
4. 映画レビューのスキーマを定義する
このセクションでは、映画アプリの主要なエンティティの構造と関係をスキーマで定義します。Movie
、User
、Actor
、Review
などのエンティティはデータベース テーブルにマッピングされ、Firebase Data Connect と GraphQL スキーマ ディレクティブを使用して関係が確立されます。実装が完了すると、高評価の映画の検索やジャンル別のフィルタリングから、ユーザーによるレビューの投稿、お気に入りのマーク付け、類似映画の探索、ベクトル検索によるテキスト入力に基づくおすすめ映画の検索まで、あらゆる処理をアプリで行うことができます。
コア エンティティと関係
Movie
型は、タイトル、ジャンル、タグなどの重要な詳細情報を保持します。アプリは、検索と映画のプロフィールにこの情報を使用します。User
タイプは、レビューやお気に入りなどのユーザー操作を追跡します。Reviews
ユーザーを映画につなぎ、ユーザー作成の評価とフィードバックをアプリに表示します。
映画、俳優、ユーザーの関係により、アプリがよりダイナミックになります。MovieActor
結合テーブルは、キャストの詳細と俳優のフィルモグラフィーを表示するのに役立ちます。FavoriteMovie
タイプでは、ユーザーが映画を「お気に入り」に登録できるため、アプリはパーソナライズされたお気に入りリストを表示し、人気のある作品をハイライト表示できます。
Movie
テーブルを設定する
Movie
型は、title
、genre
、releaseYear
、rating
などのフィールドを含む、映画エンティティのメイン構造を定義します。
コード スニペットを dataconnect/schema/schema.gql
ファイルにコピーして貼り付けます。
type Movie
@table {
id: UUID! @default(expr: "uuidV4()")
title: String!
imageUrl: String!
releaseYear: Int
genre: String
rating: Float
description: String
tags: [String]
}
重要ポイント:
- id:
@default(expr: "uuidV4()")
を使用して生成される、映画ごとに一意の UUID。
MovieMetadata
テーブルを設定する
MovieMetadata
型は、Movie
型と1 対 1 の関係を確立します。映画の監督などの追加データが含まれます。
コード スニペットを dataconnect/schema/schema.gql
ファイルにコピーして貼り付けます。
type MovieMetadata
@table {
# @ref creates a field in the current table (MovieMetadata)
# It is a reference that holds the primary key of the referenced type
# In this case, @ref(fields: "movieId", references: "id") is implied
movie: Movie! @ref
# movieId: UUID <- this is created by the above @ref
director: String
}
重要ポイント:
- 映画! @ref:
Movie
型を参照し、外部キー関係を確立します。
Actor
テーブルを設定する
コード スニペットを dataconnect/schema/schema.gql
ファイルにコピーして貼り付けます。
type Actor @table {
id: UUID!
imageUrl: String!
name: String! @col(name: "name", dataType: "varchar(30)")
}
Actor
型は、映画データベース内の俳優を表します。各俳優は複数の映画に出演し、多対多のリレーションを形成できます。
MovieActor
テーブルを設定する
コード スニペットを dataconnect/schema/schema.gql
ファイルにコピーして貼り付けます。
type MovieActor @table(key: ["movie", "actor"]) {
# @ref creates a field in the current table (MovieActor) that holds the primary key of the referenced type
# In this case, @ref(fields: "id") is implied
movie: Movie!
# movieId: UUID! <- this is created by the implied @ref, see: implicit.gql
actor: Actor!
# actorId: UUID! <- this is created by the implied @ref, see: implicit.gql
role: String! # "main" or "supporting"
}
重要ポイント:
- movie: Movie 型を参照し、外部キー movieId: UUID! を暗黙的に生成します。
- actor: Actor タイプを参照し、外部キー actorId: UUID! を暗黙的に生成します。
- role: 映画での俳優の役割を定義します(例: 「main」または「supporting」)。
User
テーブルを設定する
User
タイプは、レビューを投稿したり、映画を高く評価したりして映画を操作するユーザー エンティティを定義します。
コード スニペットを dataconnect/schema/schema.gql
ファイルにコピーして貼り付けます。
type User
@table {
id: String! @col(name: "auth_uid")
username: String! @col(dataType: "varchar(50)")
# The following are generated from the @ref in the Review table
# reviews_on_user
# movies_via_Review
}
FavoriteMovie
テーブルを設定する
FavoriteMovie
型は、ユーザーとお気に入りの映画との多対多の関係を処理する結合テーブルです。各テーブルは、User
を Movie
にリンクします。
コード スニペットを dataconnect/schema/schema.gql
ファイルにコピーして貼り付けます。
type FavoriteMovie
@table(name: "FavoriteMovies", singular: "favorite_movie", plural: "favorite_movies", key: ["user", "movie"]) {
# @ref is implicit
user: User!
movie: Movie!
}
重要ポイント:
- movie: Movie 型を参照し、外部キー
movieId: UUID!
を暗黙的に生成します。 - user: ユーザータイプを参照し、暗黙的に外部キー
userId: UUID!
を生成します。
Review
テーブルを設定する
Review
タイプはレビュー エンティティを表し、User
タイプと Movie
タイプを多対多の関係でリンクします(1 人のユーザーが複数のレビューを投稿でき、各映画に複数のレビューを投稿できます)。
コード スニペットを dataconnect/schema/schema.gql
ファイルにコピーして貼り付けます。
type Review @table(name: "Reviews", key: ["movie", "user"]) {
id: UUID! @default(expr: "uuidV4()")
user: User!
movie: Movie!
rating: Int
reviewText: String
reviewDate: Date! @default(expr: "request.time")
}
重要ポイント:
- user: レビューを投稿したユーザーを参照します。
- movie: 審査対象の映画を参照します。
- reviewDate:
@default(expr: "request.time")
を使用してレビューが作成された時刻に自動的に設定されます。
自動生成されるフィールドとデフォルト
このスキーマでは、@default(expr: "uuidV4()")
などの式を使用して一意の ID とタイムスタンプを自動的に生成します。たとえば、Movie
型と Review
型の id
フィールドには、新しいレコードが作成されると UUID が自動的に入力されます。
スキーマが定義されたので、映画アプリのデータ構造と関係の基盤ができました。
5. 人気映画と最新映画を取得する
このセクションでは、ローカル エミュレータに映画のモックデータを挿入してから、コネクタ(クエリ)と TypeScript コードを実装し、ウェブアプリでこれらのコネクタを呼び出します。最終的には、アプリでデータベースから直接、評価の高い最新の映画を動的に取得して表示できるようになります。
映画、俳優、レビューの架空のデータを挿入する
- VSCode で
dataconnect/moviedata_insert.gql
を開きます 。Firebase Data Connect 拡張機能のエミュレータが実行されていることを確認します。 - ファイルの上部に [Run (local)] ボタンが表示されます。これをクリックして、モック映画データをデータベースに挿入します。
- [Data Connect Execution] ターミナルで、データが正常に追加されたことを確認します。
コネクタを実装する
dataconnect/movie-connector/queries.gql
を開きます。コメントに基本的なListMovies
クエリが示されています。 このクエリは、すべての映画とその詳細(query ListMovies @auth(level: PUBLIC) { movies { id title imageUrl releaseYear genre rating tags description } }
id
、title
、releaseYear
など)をフェッチしますが、映画を並べ替えることはありません。- 既存の
ListMovies
クエリを次のクエリに置き換えて、並べ替えと上限のオプションを追加します。# List subset of fields for movies query ListMovies($orderByRating: OrderDirection, $orderByReleaseYear: OrderDirection, $limit: Int) @auth(level: PUBLIC) { movies( orderBy: [ { rating: $orderByRating }, { releaseYear: $orderByReleaseYear } ] limit: $limit ) { id title imageUrl releaseYear genre rating tags description } }
- [実行(ローカル)] ボタンをクリックして、ローカル データベースに対してクエリを実行します。実行前に、構成ペインでクエリ変数を入力することもできます。
重要ポイント:
movies()
: データベースから映画データを取得する GraphQL クエリ フィールド。orderByRating
: 映画を評価(昇順/降順)で並べ替えるパラメータ。orderByReleaseYear
: 映画を公開年(昇順/降順)で並べ替えるパラメータ。limit
: 返される映画の数を制限します。
ウェブアプリにクエリを統合する
この Codelab のパートでは、前のセクションで定義したクエリをウェブアプリで使用します。Firebase Data Connect エミュレータは、.gql
ファイル(特に schema.gql
、queries.gql
、mutations.gql
)と connector.yaml
ファイルの情報に基づいて SDK を生成します。これらの SDK は、アプリ内で直接呼び出すことができます。
MovieService
(app/src/lib/MovieService.tsx
)で、先頭の import ステートメントのコメント化を解除します。 関数import { listMovies, ListMoviesData, OrderDirection } from "@movie/dataconnect";
listMovies
、レスポンス タイプListMoviesData
、列挙型OrderDirection
はすべて、以前に定義したスキーマとクエリに基づいて Firebase Data Connect エミュレータによって生成された SDK です。handleGetTopMovies
関数とhandleGetLatestMovies
関数を次のコードに置き換えます。// Fetch top-rated movies export const handleGetTopMovies = async ( limit: number ): Promise<ListMoviesData["movies"] | null> => { try { const response = await listMovies({ orderByRating: OrderDirection.DESC, limit, }); return response.data.movies; } catch (error) { console.error("Error fetching top movies:", error); return null; } }; // Fetch latest movies export const handleGetLatestMovies = async ( limit: number ): Promise<ListMoviesData["movies"] | null> => { try { const response = await listMovies({ orderByReleaseYear: OrderDirection.DESC, limit, }); return response.data.movies; } catch (error) { console.error("Error fetching latest movies:", error); return null; } };
重要ポイント:
listMovies
:listMovies
クエリを呼び出して映画のリストを取得する自動生成関数。評価や公開年で並べ替えたり、結果の数を制限したりするオプションがあります。ListMoviesData
: アプリのホームページにトップ 10 と最新の映画を表示するために使用される結果タイプ。
実例を見る
ウェブアプリを再読み込みして、クエリが実行されていることを確認します。ホームページに映画のリストが動的に表示され、ローカル データベースから直接データが取得されるようになりました。設定したデータが反映され、高評価の映画や最新の映画がシームレスに表示されます。
6. 映画と俳優の詳細を表示する
このセクションでは、一意の ID を使用して映画や俳優の詳細情報を取得する機能を実装します。これには、それぞれのテーブルからデータを取得するだけでなく、関連するテーブルを結合して、映画のレビューや俳優のフィルモグラフィーなどの包括的な詳細情報を表示することも含まれます。
コネクタを実装する
- プロジェクトで
dataconnect/movie-connector/queries.gql
を開きます 。 - 次のクエリを追加して、映画と俳優の詳細を取得します。
# Get movie by id query GetMovieById($id: UUID!) @auth(level: PUBLIC) { movie(id: $id) { id title imageUrl releaseYear genre rating description tags metadata: movieMetadatas_on_movie { director } mainActors: actors_via_MovieActor(where: { role: { eq: "main" } }) { id name imageUrl } supportingActors: actors_via_MovieActor( where: { role: { eq: "supporting" } } ) { id name imageUrl } reviews: reviews_on_movie { id reviewText reviewDate rating user { id username } } } } # Get actor by id query GetActorById($id: UUID!) @auth(level: PUBLIC) { actor(id: $id) { id name imageUrl mainActors: movies_via_MovieActor(where: { role: { eq: "main" } }) { id title genre tags imageUrl } supportingActors: movies_via_MovieActor( where: { role: { eq: "supporting" } } ) { id title genre tags imageUrl } } }
- 変更を保存してクエリを確認します。
重要ポイント:
movie()
/actor()
:Movies
テーブルまたはActors
テーブルから 1 本の映画または俳優を取得するための GraphQL クエリ フィールド。_on_
: これにより、外部キー関係を持つ関連する型のフィールドに直接アクセスできます。たとえば、reviews_on_movie
は特定の映画に関連するすべてのレビューを取得します。_via_
: 結合テーブルを介して多対多の関係を移動するために使用されます。たとえば、actors_via_MovieActor
はMovieActor
結合テーブルを介してActor
タイプにアクセスし、where
条件は役割(「メイン」や「サポート」など)に基づいて俳優をフィルタします。
モックデータを入力してクエリをテストする
- Data Connect 実行ペインで、次のようなモック ID を入力してクエリをテストできます。
{"id": "550e8400-e29b-41d4-a716-446655440000"}
GetMovieById
の [実行(ローカル)] をクリックして、「Quantum Paradox」(上記の ID が関連付けられているモック映画)の詳細を取得します。
ウェブアプリにクエリを統合する
MovieService
(app/src/lib/MovieService.tsx
)で、次のインポートをコメント化解除します。import { getMovieById, GetMovieByIdData } from "@movie/dataconnect"; import { GetActorByIdData, getActorById } from "@movie/dataconnect";
handleGetMovieById
関数とhandleGetActorById
関数を次のコードに置き換えます。// Fetch movie details by ID export const handleGetMovieById = async ( movieId: string ) => { try { const response = await getMovieById({ id: movieId }); if (response.data.movie) { return response.data.movie; } return null; } catch (error) { console.error("Error fetching movie:", error); return null; } }; // Calling generated SDK for GetActorById export const handleGetActorById = async ( actorId: string ): Promise<GetActorByIdData["actor"] | null> => { try { const response = await getActorById({ id: actorId }); if (response.data.actor) { return response.data.actor; } return null; } catch (error) { console.error("Error fetching actor:", error); return null; } };
重要ポイント:
getMovieById
/getActorById
: 定義したクエリを呼び出して、特定の映画や俳優の詳細情報を取得する自動生成関数です。GetMovieByIdData
/GetActorByIdData
: 映画や俳優の詳細をアプリに表示するために使用される結果タイプです。
実例を見る
ウェブアプリのホームページに移動します。映画をクリックすると、関連テーブルから取得された俳優やレビューなど、映画の詳細情報がすべて表示されます。同様に、俳優をクリックすると、その俳優が出演した映画が表示されます。
7. ユーザー認証を処理する
このセクションでは、Firebase Authentication を使用してユーザーのログインとログアウト機能を実装します。また、Firebase Authentication のデータを使用して Firebase DataConnect でユーザーデータを直接取得またはアップサートすることで、アプリ内で安全なユーザー管理を実現します。
コネクタを実装する
dataconnect/movie-connector/
でmutations.gql
を開く 。- 次のミューテーションを追加して、現在の認証済みユーザーを作成または更新します。
# Create or update the current authenticated user mutation UpsertUser($username: String!) @auth(level: USER) { user_upsert( data: { id_expr: "auth.uid" username: $username } ) }
重要ポイント:
id_expr: "auth.uid"
:auth.uid
を使用します。これは、ユーザーやアプリではなく Firebase Authentication から直接提供されるため、ユーザー ID が安全かつ自動的に処理されるようになり、セキュリティが強化されます。
現在のユーザーを取得する
dataconnect/movie-connector/
でqueries.gql
を開く 。- 次のクエリを追加して、現在のユーザーを取得します。
# Get user by ID query GetCurrentUser @auth(level: USER) { user(key: { id_expr: "auth.uid" }) { id username reviews: reviews_on_user { id rating reviewDate reviewText movie { id title } } favoriteMovies: favorite_movies_on_user { movie { id title genre imageUrl releaseYear rating description tags metadata: movieMetadatas_on_movie { director } } } } }
重要ポイント:
auth.uid
: Firebase Authentication から直接取得されるため、ユーザー固有のデータに安全にアクセスできます。_on_
フィールド: これらのフィールドは結合テーブルを表します。reviews_on_user
: 映画のid
やtitle
など、ユーザーに関連するすべてのレビューを取得します。favorite_movies_on_user
: ユーザーがお気に入りとしてマークしたすべての映画を取得します。genre
、releaseYear
、rating
、metadata
などの詳細情報も取得します。
ウェブアプリにクエリを統合する
MovieService
(app/src/lib/MovieService.tsx
)で、次のインポートをコメント化解除します。import { upsertUser } from "@movie/dataconnect"; import { getCurrentUser, GetCurrentUserData } from "@movie/dataconnect";
handleAuthStateChange
関数とhandleGetCurrentUser
関数を次のコードに置き換えます。// Handle user authentication state changes and upsert user export const handleAuthStateChange = ( auth: any, setUser: (user: User | null) => void ) => { return onAuthStateChanged(auth, async (user) => { if (user) { setUser(user); const username = user.email?.split("@")[0] || "anon"; await upsertUser({ username }); } else { setUser(null); } }); }; // Fetch current user profile export const handleGetCurrentUser = async (): Promise< GetCurrentUserData["user"] | null > => { try { const response = await getCurrentUser(); return response.data.user; } catch (error) { console.error("Error fetching user profile:", error); return null; } };
重要ポイント:
handleAuthStateChange
: この関数は、認証状態の変化をリッスンします。ユーザーがログインすると、ユーザーのデータが設定され、upsertUser
ミューテーションが呼び出されて、データベース内のユーザー情報が作成または更新されます。handleGetCurrentUser
:getCurrentUser
クエリを使用して現在のユーザーのプロフィールを取得します。これにより、ユーザーのクチコミとお気に入りの映画が取得されます。
実例を見る
ナビゲーション バーの [Google でログイン] ボタンをクリックします。Firebase Authentication エミュレータを使用してログインできます。ログインしたら、[プロフィール] をクリックします。現時点では空ですが、アプリでユーザー固有のデータ処理の基盤が設定されました。
8. ユーザー操作を実装する
この Codelab のセクションでは、映画レビュー アプリにユーザー操作を実装します。具体的には、ユーザーがお気に入りの映画を管理したり、レビューを投稿または削除したりできるようにします。
ユーザーが映画を [お気に入り] に追加できるようにする
このセクションでは、ユーザーが映画を「お気に入り」に登録できるようにデータベースを設定します。
コネクタを実装する
dataconnect/movie-connector/
でmutations.gql
を開く 。- 映画のお気に入り登録を処理する次のミューテーションを追加します。
# Add a movie to the user's favorites list mutation AddFavoritedMovie($movieId: UUID!) @auth(level: USER) { favorite_movie_upsert(data: { userId_expr: "auth.uid", movieId: $movieId }) } # Remove a movie from the user's favorites list mutation DeleteFavoritedMovie($movieId: UUID!) @auth(level: USER) { favorite_movie_delete(key: { userId_expr: "auth.uid", movieId: $movieId }) }
重要ポイント:
userId_expr: "auth.uid"
: Firebase Authentication から直接提供されるauth.uid
を使用します。これにより、認証されたユーザーのデータのみがアクセスまたは変更されます。
映画がお気に入りに登録されているかどうかを確認する
dataconnect/movie-connector/
でqueries.gql
を開く 。- 映画がお気に入りに登録されているかどうかを確認する次のクエリを追加します。
query GetIfFavoritedMovie($movieId: UUID!) @auth(level: USER) { favorite_movie(key: { userId_expr: "auth.uid", movieId: $movieId }) { movieId } }
重要ポイント:
auth.uid
: Firebase Authentication を使用して、ユーザー固有のデータに安全にアクセスできます。favorite_movie
:favorite_movies
結合テーブルをチェックし、特定の映画が現在のユーザーによってお気に入りとしてマークされているかどうかを確認します。
ウェブアプリにクエリを統合する
MovieService
(app/src/lib/MovieService.tsx
)で、次のインポートをコメント化解除します。import { addFavoritedMovie, deleteFavoritedMovie, getIfFavoritedMovie } from "@movie/dataconnect";
handleAddFavoritedMovie
、handleDeleteFavoritedMovie
、handleGetIfFavoritedMovie
関数を次のコードに置き換えます。// Add a movie to user's favorites export const handleAddFavoritedMovie = async ( movieId: string ): Promise<void> => { try { await addFavoritedMovie({ movieId }); } catch (error) { console.error("Error adding movie to favorites:", error); throw error; } }; // Remove a movie from user's favorites export const handleDeleteFavoritedMovie = async ( movieId: string ): Promise<void> => { try { await deleteFavoritedMovie({ movieId }); } catch (error) { console.error("Error removing movie from favorites:", error); throw error; } }; // Check if the movie is favorited by the user export const handleGetIfFavoritedMovie = async ( movieId: string ): Promise<boolean> => { try { const response = await getIfFavoritedMovie({ movieId }); return !!response.data.favorite_movie; } catch (error) { console.error("Error checking if movie is favorited:", error); return false; } };
重要ポイント:
handleAddFavoritedMovie
とhandleDeleteFavoritedMovie
: ミューテーションを使用して、ユーザーのお気に入りから映画を安全に追加または削除します。handleGetIfFavoritedMovie
:getIfFavoritedMovie
クエリを使用して、映画がユーザーによってお気に入りとしてマークされているかどうかを確認します。
実例を見る
映画カードと映画の詳細ページでハートのアイコンをクリックすると、映画をお気に入りに追加またはお気に入りを解除できるようになりました。また、お気に入りの映画はプロフィール ページで確認できます。
ユーザーがレビューを投稿または削除できるようにする
次に、アプリにユーザー レビューを管理するセクションを実装します。
コネクタを実装する
mutations.gql
(dataconnect/movie-connector/mutations.gql
)で、次のミューテーションを追加します。
# Add a review for a movie
mutation AddReview($movieId: UUID!, $rating: Int!, $reviewText: String!)
@auth(level: USER) {
review_insert(
data: {
userId_expr: "auth.uid"
movieId: $movieId
rating: $rating
reviewText: $reviewText
reviewDate_date: { today: true }
}
)
}
# Delete a user's review for a movie
mutation DeleteReview($movieId: UUID!) @auth(level: USER) {
review_delete(key: { userId_expr: "auth.uid", movieId: $movieId })
}
重要ポイント:
userId_expr: "auth.uid"
: レビューが認証済みユーザーに関連付けられるようにします。reviewDate_date: { today: true }
: DataConnect を使用してレビューの現在の日付が自動的に生成されるため、手動入力は不要です。
ウェブアプリにクエリを統合する
MovieService
(app/src/lib/MovieService.tsx
)で、次のインポートをコメント化解除します。import { addReview, deleteReview } from "@movie/dataconnect";
handleAddReview
関数とhandleDeleteReview
関数を次のコードに置き換えます。// Add a review to a movie export const handleAddReview = async ( movieId: string, rating: number, reviewText: string ): Promise<void> => { try { await addReview({ movieId, rating, reviewText }); } catch (error) { console.error("Error adding review:", error); throw error; } }; // Delete a review from a movie export const handleDeleteReview = async (movieId: string): Promise<void> => { try { await deleteReview({ movieId }); } catch (error) { console.error("Error deleting review:", error); throw error; } };
重要ポイント:
handleAddReview
:addReview
ミューテーションを呼び出して、指定した映画のレビューを追加し、認証済みユーザーに安全にリンクします。handleDeleteReview
:deleteReview
ミューテーションを使用して、認証済みユーザーによる映画のレビューを削除します。
実例を見る
ユーザーは映画の詳細ページで映画のレビューを投稿できるようになりました。また、プロフィール ページでレビューを表示、削除することもできます。これにより、アプリとのやり取りを完全に管理できます。
9. 高度なフィルタと部分一致
このセクションでは、ユーザーが評価や公開年に基づいて映画を検索したり、ジャンルやタグでフィルタしたり、タイトルや説明で部分一致検索を行ったり、複数のフィルタを組み合わせてより正確な結果を得たりできる高度な検索機能を実装します。
コネクタを実装する
dataconnect/movie-connector/
でqueries.gql
を開きます。- さまざまな検索機能をサポートするには、次のクエリを追加します。
# Search for movies, actors, and reviews query SearchAll( $input: String $minYear: Int! $maxYear: Int! $minRating: Float! $maxRating: Float! $genre: String! ) @auth(level: PUBLIC) { moviesMatchingTitle: movies( where: { _and: [ { releaseYear: { ge: $minYear } } { releaseYear: { le: $maxYear } } { rating: { ge: $minRating } } { rating: { le: $maxRating } } { genre: { contains: $genre } } { title: { contains: $input } } ] } ) { id title genre rating imageUrl } moviesMatchingDescription: movies( where: { _and: [ { releaseYear: { ge: $minYear } } { releaseYear: { le: $maxYear } } { rating: { ge: $minRating } } { rating: { le: $maxRating } } { genre: { contains: $genre } } { description: { contains: $input } } ] } ) { id title genre rating imageUrl } actorsMatchingName: actors(where: { name: { contains: $input } }) { id name imageUrl } reviewsMatchingText: reviews(where: { reviewText: { contains: $input } }) { id rating reviewText reviewDate movie { id title } user { id username } } }
重要ポイント:
_and
演算子: 複数の条件を 1 つのクエリに結合し、releaseYear
、rating
、genre
などの複数のフィールドで検索をフィルタできます。contains
演算子: フィールド内のテキストの部分一致を検索します。このクエリでは、title
、description
、name
、reviewText
内で一致するものを検索します。where
句: データのフィルタ条件を指定します。各セクション(映画、俳優、レビュー)では、where
句を使用して検索の特定の条件を定義します。
ウェブアプリにクエリを統合する
MovieService
(app/src/lib/MovieService.tsx
)で、次のインポートをコメント化解除します。import { searchAll, SearchAllData } from "@movie/dataconnect";
handleSearchAll
関数を次のコードに置き換えます。// Function to perform the search using the query and filters export const handleSearchAll = async ( searchQuery: string, minYear: number, maxYear: number, minRating: number, maxRating: number, genre: string ): Promise<SearchAllData | null> => { try { const response = await searchAll({ input: searchQuery, minYear, maxYear, minRating, maxRating, genre, }); return response.data; } catch (error) { console.error("Error performing search:", error); return null; } };
重要ポイント:
handleSearchAll
: この関数は、searchAll
クエリを使用してユーザーの入力に基づいて検索を行い、年、評価、ジャンル、テキストの部分一致などのパラメータで結果をフィルタします。
実例を見る
ウェブアプリのナビゲーション バーから [検索オプション] ページに移動します。さまざまなフィルタと入力を使用して映画、俳優、レビューを検索し、詳細でカスタマイズされた検索結果を取得できるようになりました。
10. 省略可: Cloud にデプロイする(課金が必要)
ローカル開発の反復処理が完了したので、スキーマ、データ、クエリをサーバーにデプロイします。これは、Firebase Data Connect VS Code 拡張機能または Firebase CLI を使用して行えます。
Firebase の料金プランをアップグレードする
Firebase Data Connect を Cloud SQL for PostgreSQL と統合するには、Firebase プロジェクトが従量課金制(Blaze)のお支払いプラン(Cloud 請求先アカウントにリンクされている)である必要があります。
- Cloud 請求先アカウントには、クレジット カードなどのお支払い方法が必要です。
- Firebase と Google Cloud を初めて使用する場合は、$300 のクレジットと無料トライアル用 Cloud 請求先アカウントを利用できるかどうかご確認ください。
- この Codelab をイベントの一環として実施する場合は、利用可能な Cloud クレジットがあるかどうかを主催者に確認してください。
プロジェクトを Blaze プランにアップグレードする手順は次のとおりです。
- Firebase コンソールで、プランをアップグレードを選択します。
- Blaze プランを選択します。画面上の手順に沿って、Cloud 請求先アカウントをプロジェクトにリンクします。
このアップグレードの一環として Cloud 請求先アカウントを作成する必要があった場合は、Firebase コンソールのアップグレード フローに移動してアップグレードを完了する必要があります。
ウェブアプリを Firebase プロジェクトに接続する
- Firebase コンソールを使用して、Firebase プロジェクトにウェブアプリを登録します。
- プロジェクトを開き、[アプリを追加] をクリックします。
- 現時点では SDK の設定と構成の設定を無視しますが、生成された
firebaseConfig
オブジェクトを必ずコピーしてください。
app/src/lib/firebase.tsx
の既存のfirebaseConfig
を、Firebase コンソールからコピーした構成に置き換えてください。const firebaseConfig = { apiKey: "API_KEY", authDomain: "PROJECT_ID.firebaseapp.com", projectId: "PROJECT_ID", storageBucket: "PROJECT_ID.firebasestorage.app", messagingSenderId: "SENDER_ID", appId: "APP_ID" };
- ウェブアプリをビルドする: VS Code に戻り、
app
フォルダで Vite を使用して、デプロイをホストするウェブアプリをビルドします。cd app npm run build
Firebase プロジェクトで Firebase Authentication を設定する
- Google ログインを使用して Firebase Authentication を設定する。
- (省略可)Firebase コンソールを使用して、Firebase Authentication のドメインを許可します(
http://127.0.0.1
など)。- [Authentication] 設定で、[Authorized Domains] に移動します。
- [ドメインの追加] をクリックして、ローカル ドメインをリストに追加します。
Firebase CLI でデプロイする
dataconnect/dataconnect.yaml
で、インスタンス ID、データベース、サービス ID がプロジェクトと一致していることを確認します。specVersion: "v1alpha" serviceId: "your-service-id" location: "us-central1" schema: source: "./schema" datasource: postgresql: database: "your-database-id" cloudSql: instanceId: "your-instance-id" connectorDirs: ["./movie-connector"]
- プロジェクトで Firebase CLI が設定されていることを確認します。
npm i -g firebase-tools firebase login --reauth firebase use --add
- ターミナルで次のコマンドを実行してデプロイします。
firebase deploy --only dataconnect,hosting
- 次のコマンドを実行して、スキーマの変更を比較します。
firebase dataconnect:sql:diff
- 変更が許容される場合は、次のコマンドを使用して変更を適用します。
firebase dataconnect:sql:migrate
Cloud SQL for PostgreSQL インスタンスが更新され、最終的にデプロイされたスキーマとデータが使用されます。ステータスは Firebase コンソールでモニタリングできます。
これで、your-project.web.app/
でアプリをライブで確認できるようになります。また、ローカル エミュレータと同様に、Firebase Data Connect パネルで [実行(本番環境)] をクリックして、本番環境にデータを追加することもできます。
11. 省略可: Firebase Data Connect によるベクトル検索(課金が必要)
このセクションでは、Firebase Data Connect を使用して映画レビュー アプリでベクトル検索を有効にします。この機能を使用すると、ベクトル エンベディングを使用して説明が類似した映画を見つけるなど、コンテンツベースの検索を行うことができます。
この手順を完了するには、この Codelab の最後の手順で Google Cloud にデプロイする必要があります。
スキーマを更新して、フィールドのエンベディングを含める
dataconnect/schema/schema.gql
で、Movie
テーブルに descriptionEmbedding
フィールドを追加します。
type Movie
# The below parameter values are generated by default with @table, and can be edited manually.
@table {
# implicitly calls @col to generates a column name. ex: @col(name: "movie_id")
id: UUID! @default(expr: "uuidV4()")
title: String!
imageUrl: String!
releaseYear: Int
genre: String
rating: Float
description: String
tags: [String]
descriptionEmbedding: Vector @col(size:768) # Enables vector search
}
重要ポイント:
descriptionEmbedding: Vector @col(size:768)
: このフィールドには映画の説明のセマンティック エンベディングが保存され、アプリでベクトルベースのコンテンツ検索が可能になります。
Vertex AI を有効にする
- 前提条件ガイドに沿って、Google Cloud から Vertex AI API を設定します。このステップは、エンベディングの生成とベクトル検索機能をサポートするために不可欠です。
- Firebase Data Connect VS Code 拡張機能を使用して [本番環境にデプロイ] をクリックし、スキーマを再デプロイして
pgvector
とベクター検索を有効にします。
データベースにエンベディングを入力する
- VS Code で
dataconnect
フォルダを開く。 optional_vector_embed.gql
で [Run(local)] をクリックして、映画のエンベディングをデータベースに入力します。
ベクトル検索クエリを追加する
dataconnect/movie-connector/queries.gql
に次のクエリを追加して、ベクトル検索を実行します。
# Search movie descriptions using L2 similarity with Vertex AI
query SearchMovieDescriptionUsingL2Similarity($query: String!)
@auth(level: PUBLIC) {
movies_descriptionEmbedding_similarity(
compare_embed: { model: "textembedding-gecko@003", text: $query }
method: L2
within: 2
limit: 5
) {
id
title
description
tags
rating
imageUrl
}
}
重要ポイント:
compare_embed
: 比較するエンベディング モデル(textembedding-gecko@003
)と入力テキスト($query
)を指定します。method
: ユークリッド距離を表す類似度メソッド(L2
)を指定します。within
: L2 距離が 2 以下の映画に検索を限定し、コンテンツの類似度が高いものを重視します。limit
: 返される結果の数を 5 に制限します。
アプリにベクトル検索機能を実装する
スキーマとクエリが設定されたので、ベクトル検索をアプリのサービスレイヤに統合します。このステップでは、ウェブアプリから検索クエリを呼び出します。
app/src/lib/
MovieService.ts
で、SDK からの次のインポートをコメント化解除します。これは他のクエリと同様に機能します。import { searchMovieDescriptionUsingL2similarity, SearchMovieDescriptionUsingL2similarityData, } from "@movie/dataconnect";
- ベクトルベースの検索をアプリに統合するには、次の関数を追加します。
// Perform vector-based search for movies based on description export const searchMoviesByDescription = async ( query: string ): Promise< | SearchMovieDescriptionUsingL2similarityData["movies_descriptionEmbedding_similarity"] | null > => { try { const response = await searchMovieDescriptionUsingL2similarity({ query }); return response.data.movies_descriptionEmbedding_similarity; } catch (error) { console.error("Error fetching movie descriptions:", error); return null; } };
重要ポイント:
searchMoviesByDescription
: この関数はsearchMovieDescriptionUsingL2similarity
クエリを呼び出し、入力テキストを渡してベクトルベースのコンテンツ検索を実行します。
実例を見る
ナビゲーション バーの [ベクトル検索] セクションに移動し、「ロマンチックでモダン」などのフレーズを入力します。検索したコンテンツに一致する映画のリストが表示されます。また、任意の映画の映画の詳細ページに移動し、ページの下部にある [類似の映画] セクションを確認することもできます。
12. まとめ
これで、ウェブアプリをご利用いただけるようになりました。独自の映画データで遊びたい場合は、Firebase Data Connect 拡張機能を使用して _insert.gql
ファイルを模倣して独自のデータを挿入するか、VS Code の Data Connect 実行ペインから追加します。