Data Connect Queries: From Definitions to Client Code

A big part of any database solution is seamlessly getting information that you may need for your app. With Data Connect, you set up schemas to model your data, and then you make queries on that data. This blog post will dive into queries, and we’ll assume that you are familiar with schemas. Feel free to read the Data Connect documentation if you need a reminder.

We’ll use the movie review app example you might already know from the documentation and our previous deep-dive blog posts.

What is a Data Connect query?

A Data Connect query is a GraphQL query: an operation that asks about specific fields from a backend datastore defined by your schema. You write queries in GraphQL, and you can use queries:

  • in your client apps, only after you’ve deployed the queries to production
  • for ad hoc execution from privileged environments like the Visual Studio Code extension, the Firebase console, and the Firebase Admin SDK. Deployment of the queries is not required for these actions.

Declaring a Query

These are the parts of a query definition in GraphQL:

  • Query type definition
  • Operation (query) name
  • Zero, one, or more than one operation argument
  • Zero, one, or more directives

Here is an example to list all movies by genre:

query ListMoviesByGenre($genre: String!) @auth(level: PUBLIC)

This line has:

  • A query type definition, letting GraphQL know this is a query
  • A ListMoviesByGenre operation (query) name
  • A single variable $genre operation argument of the String type. Every query argument requires a type declaration, a built-in supported data type like String, or a custom, schema-defined type like Movie_Key (which is a key scalar).
  • A single directive, @auth, which specifies only logged-in users, can execute this query. For this movie data that’s appropriate for public viewing, the PUBLIC level is sufficient authorization.

Anatomy of a Query

Now that we know how to declare a query, what does a query in its entirety look like? It is broken down into the following parts:

  • Declaration to create a query, as described in the previous section
  • Tables from which the query needs to get data
  • Any filters
  • Fields to return

Here is an example query to get all movies of a particular genre ordered by highest rating to lowest:

query MoviesByTopRating($genre: String) @auth(level: PUBLIC) {
  mostPopular: movies(
     where: { genre: { eq: $genre } }, orderBy: { rating: DESC }
  ) {
    id
    title
    genre
    description
  }
}

The first line is described in the “Declaring a Query” section. Let’s take a look at the other important pieces:

  • movies specifies the table in the schema from which the query needs to get data
  • where and orderBy are filters applied to retrieving the data
  • id, title, genre, and description are the fields to be returned by this query for all of the movies that fit the genre

Directives

Let’s talk about directives - like @auth. Data Connect has three directives for queries to define their behavior:

  • @auth defines the authentication policy, and it is required for the query to be accessible by client apps
  • @check verifies that specified fields are present in query results
  • @redact omits query results from the client’s response

For more information, there are more in-depth descriptions and resources in the documentation about directives for queries.

Generated Fields

When you create a schema, Data Connect’s local tooling automatically generates fields based on the types and type relationships in your schema. These generated fields are necessary for writing your own queries to be deployed and used in your client code, or run ad hoc in privileged environments

As an example of a generated field, say the Movie type is defined as:

type Movie @table(name: "Movies") @index(fields: [releaseYear, rating], order: [ASC, DESC]) {
  id: UUID! @default(expr: "uuidV4()")
  title: String!
  releaseYear: Int @index
  genre: String @col(dataType: "varchar(30)")
  rating: Int
  description: String
}

With the schema definition, Data Connect generates fields for single-table use cases, as seen in query.gql. It creates a field to get:

  • a singular record from the Movie table, given the id for the movie. The field name is the same as the table name, unless otherwise defined - movie in this case.
movie(id: UUID, key: Movie_Key): Movie @fdc_generated(from: "Movie", purpose: QUERY_SINGLE)
  • a list of movies, given optional filtering conditions. The field name is the same as the table name with a “s” at the end, unless otherwise defined - movies in this case.
movies(where: Movie_Filter, orderBy: [Movie_Order!], offset: Int, limit: Int = 100): [Movie!]! @fdc_generated(from: "Movie", purpose: QUERY_MULTIPLE)

(In both of these tables, a directive that this blog post didn’t cover is @fdc_generated - you will never be writing this on your own, so don’t worry too much about it. It says which table the generated field is coming from and whether it’s querying for a single record or multiple records).

Data Connect also generates fields for multi-table, relational operations with explicit names. This will be in a future blog post, as there is a lot to cover.

Where can I find these fields?

With the Data Connect Visual Studio Code extension, there are several ways to see and use these generated fields after you begin editing your schema:

  • They are listed under the Schema Explorer
  • Their source is generated to dataconnect/.dataconnect/main/query.gql
  • They are browsable in automatic local documentation, accessible with the View reference docs link
  • They are incorporated into ad hoc queries generated when you click CodeLens buttons

Once you deploy your schema, you can view the generated fields with reference documentation in the Firebase console and use the data tab to prototype queries.

Using Generated Fields for Queries

With the generated fields, you can now write queries to retrieve the information needed for your app. Your client code can only use these queries and not the generated fields. Additionally, the client only gets to see the fields selected in queries. If there’s a secret field that you don’t want clients to see, then clients cannot see it since they don’t have direct access to the database.

For example, you can use the generated movie field to create your own query, where you pass in the movie id:

query GetMovieById($id: UUID!) @auth(level: PUBLIC) {
  movie(id: $id) {
    id
    title
    imageUrl
    genre
  }
}

For listing movies, you can use the generated movies field:

query ListMovies @auth(level: PUBLIC) {
  movies {
    id
    title
    imageUrl
    genre
  }
}

Using queries

Now that you’ve defined your queries, you’re ready to use them in your app code. How do you do that? Data Connect automatically generates SDKs for you to use, bridging the gap between your queries defined in GraphQL and your application platform. You need to set up these generated SDKs, and then you can call the generated functions in your app code. Feel free to read about what generated SDKs are and how to use them in Data Connect.

Try it out!

Queries are a crucial part of any app, and hopefully this blog post clarifies how to define and use queries in Data Connect. If you need more information about Data Connect, check out the documentation, and you can also check out other deep-dive articles. As it’s a new product, we’d love to hear your opinion and any feature requests you have as we’re hard at work improving and adding more features to the product. If you haven’t already, give Data Connect a try today - we can’t wait to see what you build with it!