Build with Firebase Data Connect (web)

1. Before you begin

FriendlyMovies app

In this codelab, you'll integrate Firebase Data Connect with a Cloud SQL database to build a movie review web app. The completed app showcases how Firebase Data Connect simplifies the process of building SQL-powered applications. It includes these features:

  • Authentication:Implement custom authentication for your app's queries and mutations, ensuring only authorized users can interact with your data.
  • GraphQL schema:Create and manage your data structures using a flexible GraphQL schema tailored to the needs of a movie review web app.
  • SQL queries and mutations:Retrieve, update, and manage data in Cloud SQL using queries and mutations powered by GraphQL.
  • Advanced search with partial string match:Use filters and search options to find movies based on fields like title, description, or tags.
  • (Optional) Vector search integration:Add content search functionality using Firebase Data Connect's vector search to provide a rich user experience based on input and preferences.

Prerequisites

You'll need a basic understanding of JavaScript .

What you'll learn

  • Set up Firebase Data Connect with local emulators.
  • Design a data schema using Data Connect and GraphQL .
  • Write and test various queries and mutations for a movie review app.
  • Learn how Firebase Data Connect generates and uses the SDK in the app.
  • Deploy your schema and manage the database efficiently.

What you'll need

2. Set up your development environment

This stage of the codelab will guide you through setting up the environment to start building your movie review app using Firebase Data Connect.

  1. Clone the project repository and install the required dependencies:
    git clone https://github.com/firebaseextended/codelab-dataconnect-web
    cd codelab-dataconnect-web
    cd ./app && npm i
    npm run dev
  2. After running these commands, open http://localhost:5173 in your browser to see the web app running locally. This serves as your front end for building the movie review app and interacting with its features.93f6648a2532c606.png
  3. Openthe cloned codelab-dataconnect-web folder using Visual Studio Code . This is where you'll define your schema, write queries, and test the functionality of the app.
  4. To use Data Connect features, install the Firebase Data Connect Visual Studio Extension .
    Alternatively, you can install the extension from the Visual Studio Code Marketplace or search for it within VS Code.b03ee38c9a81b648.png
  5. Open or create a new Firebase project in the Firebase console .
  6. Connect your Firebase project to the Firebase Data Connect VSCode extension. In the extension, do the following:
    1. Click the Sign inbutton.
    2. Click Connect a Firebase Projectand select your Firebase project.
    4bb2fbf8f9fac29b.png
  7. Start the Firebase emulators using the Firebase Data Connect VS Code extension:
    Click Start Emulators, and then confirm that the emulators are running in the terminal.6d3d95f4cb708db1.png

3. Review the starter codebase

In this section, you'll explore key areas of the app's starter codebase. While the app is missing some functionality, it's helpful to understand the overall structure.

Folder and file structure

The following sub-sections provide an overview of the app's folder and file structure.

The dataconnect/ directory

Contains Firebase Data Connect configurations, connectors (which define queries and mutations), and schema files.

  • schema/schema.gql : Defines the GraphQL schema
  • connector/queries.gql : Queries needed in your app
  • connector/mutations.gql : Mutations needed in your app
  • connector/connector.yaml : Configuration file for SDK generation

The app/src/ directory

Contains the application logic and interaction with Firebase Data Connect.

  • firebase.ts : Configuration to connect to a Firebase App in your Firebase project.
  • lib/dataconnect-sdk/ : Contains the generated SDK. You can edit the location of SDK generation in connector/connector.yaml file and SDKs will be automatically generated any time you define a query or mutation.

4. Define a schema for movie reviews

In this section, you'll define the structure and relationships between the key entities in the movie application in a schema. Entities such as Movie , User , Actor , and Review are mapped to database tables, with relationships established using Firebase Data Connect and GraphQL schema directives. Once it's in place, your app will be ready to handle everything from searching for top-rated movies and filtering by genre to letting users leave reviews, mark favorites, explore similar movies, or find recommended movies based on text input through vector search.

Core entities and relationships

The Movie type holds key details like title, genre, and tags, which the app uses for searches and movie profiles. The User type tracks user interactions, like reviews and favorites. Reviews connect users to movies, letting the app show user-generated ratings and feedback.

Relationships between movies, actors, and users make the app more dynamic. The MovieActor join table helps display cast details and actor filmographies. The FavoriteMovie type allows users to favorite movies, so the app can show a personalized favorites list and highlight popular picks.

Set up the Movie table

The Movie type defines the main structure for a movie entity, including fields like title , genre , releaseYear , and rating .

Copy and pastethe code snippet into your dataconnect/schema/schema.gql file:

  type 
  
 Movie 
  
 @ 
 table 
  
 { 
  
 id 
 : 
  
 UUID 
 ! 
  
 @ 
 default 
 ( 
 expr 
 : 
  
 "uuidV4()" 
 ) 
  
 title 
 : 
  
 String 
 ! 
  
 imageUrl 
 : 
  
 String 
 ! 
  
 releaseYear 
 : 
  
 Int 
  
 genre 
 : 
  
 String 
  
 rating 
 : 
  
 Float 
  
 description 
 : 
  
 String 
  
 tags 
 : 
  
 [ 
 String 
 ] 
 } 
 

Key Takeaways:

  • id:A unique UUID for each movie, generated using @default(expr: "uuidV4()") .

The MovieMetadata type establishes a one-to-one relationshipwith the Movie type. It includes additional data such as the movie's director.

Copy and pastethe code snippet into your dataconnect/schema/schema.gql file:

  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 
 } 
 

Key Takeaways:

  • Movie! @ref:References the Movie type, establishing a foreign key relationship.

Set up the Actor table

Copy and pastethe code snippet into your dataconnect/schema/schema.gql file:

  type 
  
 Actor 
  
 @ 
 table 
  
 { 
  
 id 
 : 
  
 UUID 
 ! 
  
 imageUrl 
 : 
  
 String 
 ! 
  
 name 
 : 
  
 String 
 ! 
  
 @ 
 col 
 ( 
 name 
 : 
  
 "name" 
 , 
  
 dataType 
 : 
  
 "varchar(30)" 
 ) 
 } 
 

The Actor type represents an actor in the movie database, where each actor can be part of multiple movies, forming a many-to-many relationship.

Set up the MovieActor table

Copy and pastethe code snippet into your dataconnect/schema/schema.gql file:

  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" 
 } 
 

Key Takeaways:

  • movie:References the Movie type, implicitly generates a foreign key movieId: UUID!.
  • actor:References the Actor type, implicitly generates a foreign key actorId: UUID!.
  • role:Defines the actor's role in the movie (e.g., "main" or "supporting").

Set up the User table

The User type defines a user entity who interacts with movies by leaving reviews or favoriting movies.

Copy and pastethe code snippet into your dataconnect/schema/schema.gql file:

  type 
  
 User 
  
 @ 
 table 
  
 { 
  
 id 
 : 
  
 String 
 ! 
  
 @ 
 col 
  
 username 
 : 
  
 String 
 ! 
  
 @ 
 col 
 ( 
 dataType 
 : 
  
 "varchar(50)" 
 ) 
  
 # 
  
 The 
  
 following 
  
 are 
  
 generated 
  
 from 
  
 the 
  
 @ 
 ref 
  
 in 
  
 the 
  
 Review 
  
 table 
  
 # 
  
 reviews_on_user 
  
 # 
  
 movies_via_Review 
 } 
 

Set up the FavoriteMovie table

The FavoriteMovie type is a join table that handles many-to-many relationshipsbetween users and their favorite movies. Each table links a User to a Movie .

Copy and pastethe code snippet into your dataconnect/schema/schema.gql file:

  type 
  
 FavoriteMovie 
  
 @ 
 table 
 ( 
 name 
 : 
  
 "FavoriteMovies" 
 , 
  
 singular 
 : 
  
 "favorite_movie" 
 , 
  
 plural 
 : 
  
 "favorite_movies" 
 , 
  
 key 
 : 
  
 [ 
 "user" 
 , 
  
 "movie" 
 ]) 
  
 { 
  
 # 
  
 @ 
 ref 
  
 is 
  
 implicit 
  
 user 
 : 
  
 User 
 ! 
  
 movie 
 : 
  
 Movie 
 ! 
 } 
 

Key Takeaways:

  • movie:References the Movie type, implicitly generates a foreign key movieId: UUID! .
  • user:References the user type, implicitly generates a foreign key userId: UUID! .

Set up the Review table

The Review type represents the review entity and links the User and Movie types in a many-to-many relationship(one user can leave many reviews, and each movie can have many reviews).

Copy and pastethe code snippet into your dataconnect/schema/schema.gql file:

  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" 
 ) 
 } 
 

Key Takeaways:

  • user:References the user who left the review.
  • movie:References the movie being reviewed.
  • reviewDate:Automatically set to the time when the review is created using @default(expr: "request.time") .

Auto-generated fields and defaults

The schema uses expressions like @default(expr: "uuidV4()") to automatically generate unique IDs and timestamps. For instance, the id field in the Movie and Review types is automatically populated with a UUID when a new record is created.

Now that the schema is defined, your movie app has a solid foundation for its data structure and relationships!

5. Retrieve top and latest movies

FriendlyMovies app

In this section, you will insert mock movie data into the local emulators, then implement the connectors (queries) and the TypeScript code to call these connectors in the web application. By the end, your app will be able to dynamically fetch and display the top-rated and latest movies directly from the database.

Insert mock movie, actor, and review data

  1. In VSCode, open dataconnect/moviedata_insert.gql . Ensure the emulators in the Firebase Data Connect extension are running.
  2. You should see a Run (local)button at the top of the file. Click this to insert the mock movie data into your database.
    e424f75e63bf2e10.png
  3. Check the Data Connect Executionterminal to confirm that the data was added successfully.
    e0943d7704fb84ea.png

Implement the connector

  1. Open dataconnect/movie-connector/queries.gql . You'll find a basic ListMovies query in the comments:
      query 
      
     ListMovies 
      
     @ 
     auth 
     ( 
     level 
     : 
      
     PUBLIC 
     ) 
      
     { 
      
     movies 
      
     { 
      
     id 
      
     title 
      
     imageUrl 
      
     releaseYear 
      
     genre 
      
     rating 
      
     tags 
      
     description 
      
     } 
     } 
     
    
    This query fetches all movies and their details (for example, id , title , releaseYear ). However, it does notsort the movies.
  2. Replacethe existing ListMovies query with the following query to add sorting and limit options:
      # 
      
     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 
      
     } 
     } 
     
    
  3. Click the Run (local)button to execute the query against your local database. You can also input the query variables in the configuration pane before running.
    c4d947115bb11b16.png

Key Takeaways:

  • movies() :GraphQL query field for fetching movie data from the database.
  • orderByRating :Parameter to sort movies by rating (ascending/descending).
  • orderByReleaseYear :Parameter to sort movies by release year (ascending/descending).
  • limit :Restricts the number of movies returned.

Integrate queries in the web app

In this part of the codelab, you'll use the queries defined in the previous section in your web app. The Firebase Data Connect emulators generate SDKs based on the information in the .gql files (specifically, schema.gql , queries.gql , mutations.gql ) and the connector.yaml file. These SDKs can be directly called in your application.

  1. In MovieService ( app/src/lib/MovieService.tsx ), uncommentthe import statement at the top:
      import 
      
     { 
      
     listMovies 
     , 
      
     ListMoviesData 
     , 
      
     OrderDirection 
      
     } 
      
     from 
      
     "@movie/dataconnect" 
     ; 
     
    
    The function listMovies , the response type ListMoviesData , and the enum OrderDirection are all SDKs generated by the Firebase Data Connect emulators based on the schema and the queries you've previously defined .
  2. Replace the handleGetTopMovies and handleGetLatestMovies functions with the following code:
      // 
      
     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 
     ; 
      
     } 
     }; 
     
    

Key Takeaways:

  • listMovies :An auto-generated function that calls the listMovies query to retrieve a list of movies. It includes options for sorting by rating or release year and limiting the number of results.
  • ListMoviesData :The result type used to display the top 10 and latest movies on the app's homepage.

See it in action

Reload your web app to see the query in action. The homepage now dynamically displays the list of movies, fetching data directly from your local database. You'll see the top-rated and latest movies appear seamlessly, reflecting the data you've just set up.

6. Display movie and actor details

In this section, you'll implement the functionality to retrieve detailed information for a movie or an actor using their unique IDs. This involves not only fetching data from their respective tables but also joining related tables to display comprehensive details, such as movie reviews and actor filmographies.

ac7fefa7ff779231.png

Implement connectors

  1. Open dataconnect/movie-connector/queries.gql in your project.
  2. Addthe following queries to retrieve movie and actor details:
      # 
      
     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 
      
     } 
      
     } 
     } 
     
    
  3. Save your changes and review the queries.

Key Takeaways:

  • movie() / actor() : GraphQL query fields for fetching a single movie or actor from the Movies or Actors table.
  • _on_ : This allows direct access to fields from an associated type that has a foreign key relationship. For example, reviews_on_movie fetches all reviews related to a specific movie.
  • _via_ : Used to navigate many-to-many relationships through a join table. For instance, actors_via_MovieActor accesses the Actor type through the MovieActor join table, and the where condition filters actors based on their role (for example, "main" or "supporting").

Test the query by inputting mock data

  1. In the Data Connect execution pane, you can test the query by inputting mock IDs, such as:
    {"id": "550e8400-e29b-41d4-a716-446655440000"}
  2. Click Run (local)for GetMovieById to retrieve the details about "Quantum Paradox" (the mock movie that the above ID relates to).

1b08961891e44da2.png

Integrate queries in the web app

  1. In MovieService ( app/src/lib/MovieService.tsx ), uncommentthe following imports:
      import 
      
     { 
      
     getMovieById 
     , 
      
     GetMovieByIdData 
      
     } 
      
     from 
      
     "@movie/dataconnect" 
     ; 
     import 
      
     { 
      
     GetActorByIdData 
     , 
      
     getActorById 
      
     } 
      
     from 
      
     "@movie/dataconnect" 
     ; 
     
    
  2. Replacethe handleGetMovieById and handleGetActorById functions with the following code:
      // 
      
     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 
     ; 
      
     } 
     }; 
     
    

Key Takeaways:

  • getMovieById / getActorById : These are auto-generated functions that call the queries you defined, retrieving detailed information for a specific movie or actor.
  • GetMovieByIdData / GetActorByIdData : These are the result types, used to display movie and actor details in the app.

See it in action

Now, go to your web app's homepage. Click on a movie, and you'll be able to view all its details, including actors and reviews—information pulled from related tables. Similarly, clicking on an actor will display the movies they were part of.

7. Handle user authentication

In this section, you'll implement user sign-in and sign-out functionality using Firebase Authentication. You'll also use Firebase Authentication data to directly retrieve or upsert user data in Firebase DataConnect, ensuring secure user management within your app.

9890838045d5a00e.png

Implement connectors

  1. Open mutations.gql in dataconnect/movie-connector/ .
  2. Add the following mutation to create or update the current authenticated user:
      # 
      
     Create 
      
     or 
      
     update 
      
     the 
      
     current 
      
     authenticated 
      
     user 
     mutation 
      
     UpsertUser 
     ( 
     $ 
     username 
     : 
      
     String 
     ! 
     ) 
      
     @ 
     auth 
     ( 
     level 
     : 
      
     USER 
     ) 
      
     { 
      
     user_upsert 
     ( 
      
     data 
     : 
      
     { 
      
     id_expr 
     : 
      
     "auth.uid" 
      
     username 
     : 
      
     $ 
     username 
      
     } 
      
     ) 
     } 
     
    

Key Takeaways:

  • id_expr: "auth.uid" : This uses auth.uid , which is provided directly by Firebase Authentication, not by the user or the app, adding an extra layer of security by ensuring the user ID is handled securely and automatically.

Fetch the current user

  1. Open queries.gql in dataconnect/movie-connector/ .
  2. Addthe following query to fetch the current user:
      # 
      
     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 
      
     } 
      
     } 
      
     } 
      
     } 
     } 
     
    

Key Takeaways:

  • auth.uid : This is retrieved directly from Firebase Authentication, ensuring secure access to user-specific data.
  • _on_ fields: These fields represent the join tables:
    • reviews_on_user : Fetches all reviews related to the user, including the movie's id and title .
    • favorite_movies_on_user : Retrieves all movies marked as favorites by the user, including detailed information like genre , releaseYear , rating , and metadata .

Integrate queries in the web app

  1. In MovieService ( app/src/lib/MovieService.tsx ), uncomment the following imports:
      import 
      
     { 
      
     upsertUser 
      
     } 
      
     from 
      
     "@movie/dataconnect" 
     ; 
     import 
      
     { 
      
     getCurrentUser 
     , 
      
     GetCurrentUserData 
      
     } 
      
     from 
      
     "@movie/dataconnect" 
     ; 
     
    
  2. Replace the handleAuthStateChange and handleGetCurrentUser functions with the following code:
      // 
      
     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 
     ; 
      
     } 
     }; 
     
    

Key Takeaways:

  • handleAuthStateChange : This function listens for authentication state changes. When a user signs in, it sets the user's data and calls the upsertUser mutation to create or update the user's information in the database.
  • handleGetCurrentUser : Fetches the current user's profile using the getCurrentUser query, which retrieves the user's reviews and favorite movies.

See it in action

Now, click on the "Sign in with Google" button in the navbar. You can sign in using the Firebase Authentication emulator. After signing in, click on "My Profile". It will be empty for now, but you've set up the foundation for user-specific data handling in your app.

8. Implement user interactions

In this section of the codelab, you'll implement user interactions in the movie review app, specifically letting users manage their favorite movies and leave or delete reviews.

b3d0ac1e181c9de9.png

Let a user favorite a movie

In this section, you'll set up the database to let users favorite a movie.

Implement connectors

  1. Open mutations.gql in dataconnect/movie-connector/ .
  2. Addthe following mutations to handle favoriting movies:
      # 
      
     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 
      
     }) 
     } 
     
    

Key Takeaways:

  • userId_expr: "auth.uid" : Uses auth.uid , which is provided directly by Firebase Authentication, ensuring that only the authenticated user's data is accessed or modified.

Check if a movie is favorited

  1. Open queries.gql in dataconnect/movie-connector/ .
  2. Addthe following query to check if a movie is favorited:
      query 
      
     GetIfFavoritedMovie 
     ( 
     $ 
     movieId 
     : 
      
     UUID 
     ! 
     ) 
      
     @ 
     auth 
     ( 
     level 
     : 
      
     USER 
     ) 
      
     { 
      
     favorite_movie 
     ( 
     key 
     : 
      
     { 
      
     userId_expr 
     : 
      
     "auth.uid" 
     , 
      
     movieId 
     : 
      
     $ 
     movieId 
      
     }) 
      
     { 
      
     movieId 
      
     } 
     } 
     
    

Key Takeaways:

  • auth.uid : Ensures secure access to user-specific data using Firebase Authentication.
  • favorite_movie : Checks the favorite_movies join table to see if a specific movie is marked as a favorite by the current user.

Integrate queries in the web app

  1. In MovieService ( app/src/lib/MovieService.tsx ), uncommentthe following imports:
      import 
      
     { 
      
     addFavoritedMovie 
     , 
      
     deleteFavoritedMovie 
     , 
      
     getIfFavoritedMovie 
      
     } 
      
     from 
      
     "@movie/dataconnect" 
     ; 
     
    
  2. Replacethe handleAddFavoritedMovie , handleDeleteFavoritedMovie , and handleGetIfFavoritedMovie functions with the following code:
      // 
      
     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 
     ; 
      
     } 
     }; 
     
    

Key Takeaways:

  • handleAddFavoritedMovie and handleDeleteFavoritedMovie : Use the mutations to add or remove a movie from the user's favorites securely.
  • handleGetIfFavoritedMovie : Uses the getIfFavoritedMovie query to check if a movie is marked as a favorite by the user.

See it in action

Now, you can favorite or unfavorite movies by clicking the heart icon on the movie cards and the movie details page. Additionally, you can view your favorite movies on your profile page.

Let users leave or delete reviews

Next, you'll implement the section for managing user reviews in the app.

Implement connectors

In mutations.gql ( dataconnect/movie-connector/mutations.gql ): Add the following mutations:

  # 
  
 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 }) 
 } 
 

Key Takeaways:

  • userId_expr: "auth.uid" : Ensures that reviews are associated with the authenticated user.
  • reviewDate_date: { today: true } : Automatically generates the current date for the review using DataConnect, eliminating the need for manual input.

Integrate queries in the web app

  1. In MovieService ( app/src/lib/MovieService.tsx ), uncommentthe following imports:
      import 
      
     { 
      
     addReview 
     , 
      
     deleteReview 
      
     } 
      
     from 
      
     "@movie/dataconnect" 
     ; 
     
    
  2. Replacethe handleAddReview and handleDeleteReview functions with the following code:
      // 
      
     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 
     ; 
      
     } 
     }; 
     
    

Key Takeaways:

  • handleAddReview : Calls the addReview mutation to add a review for the specified movie, securely linking it to the authenticated user.
  • handleDeleteReview : Uses the deleteReview mutation to remove a review for a movie by the authenticated user.

See it in action

Users can now leave reviews for movies on the movie details page. They can also view and delete their reviews on their profile page, giving them full control over their interactions with the app.

9. Advanced filters and partial text matching

In this section, you'll implement advanced search capabilities, allowing users to search movies based on a range of ratings and release years, filter by genres and tags, perform partial text matching in titles or descriptions, and even combine multiple filters for more precise results.

ece70ee0ab964e28.png

Implement connectors

  1. Open queries.gql in dataconnect/movie-connector/ .
  2. Addthe following query to support various search capabilities:
      # 
      
     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 
      
     } 
      
     } 
     } 
     
    

Key Takeaways:

  • _and operator: Combines multiple conditions in a single query, allowing the search to be filtered by several fields like releaseYear , rating , and genre .
  • contains operator: Searches for partial text matches within fields. In this query, it looks for matches within title , description , name , or reviewText .
  • where clause: Specifies the conditions for filtering data. Each section (movies, actors, reviews) uses a where clause to define the specific criteria for the search.

Integrate queries in the web app

  1. In MovieService ( app/src/lib/MovieService.tsx ), uncommentthe following imports:
      import 
      
     { 
      
     searchAll 
     , 
      
     SearchAllData 
      
     } 
      
     from 
      
     "@movie/dataconnect" 
     ; 
     
    
  2. Replacethe handleSearchAll function with the following code:
      // 
      
     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 
     ; 
      
     } 
     }; 
     
    

Key Takeaways:

  • handleSearchAll : This function uses the searchAll query to perform a search based on the user's input, filtering results by parameters like year, rating, genre, and partial text matches.

See it in action

Head over to the "Advanced Search" page from the navbar in the web app. You can now search for movies, actors, and reviews using various filters and inputs, getting detailed and tailored search results.

10. Optional: Deploy to Cloud (billing required)

Now that you've worked through the local development iteration, it's time to deploy your schema, data, and queries to the server. This can be done using the Firebase Data Connect VS Code extension or the Firebase CLI.

Upgrade your Firebase pricing plan

To integrate Firebase Data Connect with Cloud SQL for PostgreSQL, your Firebase project needs to be on the pay-as-you go (Blaze) pricing plan , which means it's linked to a Cloud Billing account .

  • A Cloud Billing account requires a payment method, like a credit card.
  • If you're new to Firebase and Google Cloud, check if you're eligible for a $300 credit and a Free Trial Cloud Billing account .
  • If you're doing this codelab as part of an event, ask your organizer if there are any Cloud credits available.

To upgrade your project to the Blaze plan, follow these steps:

  1. In the Firebase console, select to upgrade your plan .
  2. Select the Blaze plan. Follow the on-screen instructions to link a Cloud Billing account to your project.
    If you needed to create a Cloud Billing account as part of this upgrade, you might need to navigate back to the upgrade flow in the Firebase console to complete the upgrade.

Connect your web app to your Firebase project

  1. Register your web app in your Firebase project using the Firebase console :
    1. Open your project, and then click Add App.
    2. Ignore the SDK setup and configuration setup for now, but make sure to copythe generated firebaseConfig object.
    7030822793e4d75b.png
  2. Replacethe existing firebaseConfig in app/src/lib/firebase.tsx with the configuration you just copied from the Firebase console.
      const 
      
     firebaseConfig 
      
     = 
      
     { 
      
     apiKey 
     : 
      
     "API_KEY" 
     , 
      
     authDomain 
     : 
      
     "PROJECT_ID.firebaseapp.com" 
     , 
      
     projectId 
     : 
      
     "PROJECT_ID" 
     , 
      
     storageBucket 
     : 
      
     "PROJECT_ID.firebasestorage.app" 
     , 
      
     messagingSenderId 
     : 
      
     "SENDER_ID" 
     , 
      
     appId 
     : 
      
     "APP_ID" 
     }; 
     
    
  3. Build the web app:Back in VS Code, in the app folder, use Vite to build the web app for hosting deployment:
    cd app
    npm run build

Set up Firebase Authentication in your Firebase project

  1. Set up Firebase Authentication with Google Sign-In.62af2f225e790ef6.png
  2. (Optional) Allow domains for Firebase Authentication using the Firebase console (for example, http://127.0.0.1 ).
    1. In the Authenticationsettings, go to Authorized Domains .
    2. Click "Add Domain" and include your local domain in the list.

c255098f12549886.png

Deploy with the Firebase CLI

  1. In dataconnect/dataconnect.yaml , ensure that your instance ID, database, and service ID match your project:
      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" 
     ] 
     
    
  2. Make sure that you have the Firebase CLI set up with your project:
    npm i -g firebase-tools
    firebase login --reauth
    firebase use --add
  3. In your terminal, run the following command to deploy:
    firebase deploy --only dataconnect,hosting
  4. Run this command to compare your schema changes:
    firebase dataconnect:sql:diff
  5. If the changes are acceptable, apply them with:
    firebase dataconnect:sql:migrate

Your Cloud SQL for PostgreSQL instance will be updated with the final deployed schema and data. You can monitor the status in the Firebase console.

You should now be able to see your app live at your-project.web.app/ . Additionally, you can click Run (Production)in the Firebase Data Connect panel, just as you did with the local emulators, to add data to the production environment.

11. Optional: Vector search with Firebase Data Connect (billing required)

In this section, you'll enable vector search in your movie review app using Firebase Data Connect. This feature allows for content-based searches, such as finding movies with similar descriptions using vector embeddings.

This step requires that you completed the last step of this codelab to deploy to Google Cloud.

4b5aca5a447d2feb.png

Update the schema to include embeddings for a field

In dataconnect/schema/schema.gql , add the descriptionEmbedding field to the Movie table:

  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 
 } 
 

Key Takeaways:

  • descriptionEmbedding: Vector @col(size:768) : This field stores the semantic embeddings of movie descriptions, enabling vector-based content search in your app.

Activate Vertex AI

  1. Follow the prerequisites guide to set up Vertex AI APIs from Google Cloud. This step is essential to support the embedding generation and vector search functionality.
  2. Re-deploy your schemato activate pgvector and vector search by clicking on "Deploy to Production" using the Firebase Data Connect VS Code extension.

Populate the database with embeddings

  1. Openthe dataconnect folder in VS Code.
  2. Click Run(local)in optional_vector_embed.gql to populate your database with embeddings for the movies.

b858da780f6ec103.png

Add a vector search query

In dataconnect/movie-connector/queries.gql , add the following query to perform vector searches:

  # 
  
 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 
  
 } 
 } 
 

Key Takeaways:

  • compare_embed : Specifies the embedding model ( textembedding-gecko@003 ) and the input text ( $query ) for comparison.
  • method : Specifies the similarity method ( L2 ), which represents the Euclidean distance.
  • within : Limits the search to movies with an L2 distance of 2 or less, focusing on close content matches.
  • limit : Restricts the number of results returned to 5.

Implement the vector search function in your app

Now that the schema and query are set up, integrate the vector search into your app's service layer. This step allows you to call the search query from your web app.

  1. In app/src/lib/ MovieService.ts , uncommentthe following imports from the SDKs, this will work like any other query.
      import 
      
     { 
      
     searchMovieDescriptionUsingL2similarity 
     , 
      
     SearchMovieDescriptionUsingL2similarityData 
     , 
     } 
      
     from 
      
     "@movie/dataconnect" 
     ; 
     
    
  2. Addthe following function to integrate vector-based search into the app:
      // 
      
     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 
     ; 
      
     } 
     }; 
     
    

Key Takeaways:

  • searchMoviesByDescription : This function calls the searchMovieDescriptionUsingL2similarity query, passing the input text to perform a vector-based content search.

See it in action

Navigate to the "Vector Search" section in the navbar and type in phrases like "romantic and modern". You'll see a list of movies that match the content you're searching for, or, go into the movie details page of any movie, and check out the similar movies section at the bottom of the page.

7b71f1c75633c1be.png

12. Conclusion

Congratulations, you should be able to use the web app! If you want to play with your own movie data, don't worry, insert your own data using the Firebase Data Connect extension by mimicking the _insert.gql files, or add them through the Data Connect execution pane in VS Code.

Learn more

Create a Mobile Website
View Site in Mobile | Classic
Share by: