↑ ↓ navigate↵ selectesc close

How Relationships Work in Data Connect in Firebase: Web Architecture & Implementation Guide

CodingCat.dev tutorial cover image

For years, choosing Firebase meant trading away relational SQL joins, strict foreign keys, and pgvector similarity search in favor of NoSQL document denormalization. Following the announcement of How Relationships Work in Data Connect by Andrea Wu on the Official Firebase Blog (2025-01-28), here is our hands-on CodingCat.dev breakdown of what actually shipped, how it works under the hood, and how we wire it into production TypeScript, Next.js, Astro, SvelteKit, React, and Angular applications. Note: Firebase Data Connect has been renamed to SQL Connect. Learn more in the SQL Connect announcement.

The Mental Model: Why How Relationships Work in Data Connect Matters for Web Devs

As a Google Developer Expert (GDE) for Firebase and Senior Solutions Engineer at CodingCat.dev, I evaluate every Firebase update through one lens: does it reduce boilerplate while keeping our runtime security and cloud bills predictable? Firebase Data Connect bridges that gap by pairing a managed Google Cloud SQL for PostgreSQL instance with a declarative GraphQL schema compiler that automatically generates type-safe TypeScript, React, and Angular SDKs alongside server-enforced @auth directives.

AJ Pro-Tip from Alex: Keep your authorization logic inside your Data Connect GraphQL operations using @auth(level: USER) and server-evaluated expressions like auth.uid and request.time rather than trusting client-supplied user IDs in query variables.

How It Works: Technical Breakdown & Implementation

One of the major benefits of using a SQL database is that relationships between data are clearly defined and organized. Firebase Data Connect supports relationships in schemas, queries, and mutations, and this blog post will dive into setting up your schema to reflect relationships. We’ll assume that you are familiar with schemas, and you can read the Data Connect documentation if you need a refresher.

Relationships in relational databases

In relational databases, each table has a primary key to identify each record in the table, and these primary keys can be used in other tables as a foreign key to refer back to the record with that primary key. This creates a relationship between the two tables. There are one-to-one, one-to-many, and many-to-many relationships, and we’ll go over each one and how to create such relationships in Data Connect.

One-to-one relationships

In one-to-one relationships, one record in a table is associated with exactly one record in another table. For example, a “Movie” table that holds information about the movie such as title and genre can have a one-to-one relationship with a “Movie Metadata” table that keeps track of additional information such as the director, release year, and rating for each movie. A movie can have one metadata record, and each metadata is for one movie.

To define the relationship, the Movie table has a primary key that’s used in the MovieMetadata table. See this below where the Movie table with movieId as the primary key and a MovieMetadata table has movieMetadataId as its primary key. In the latter table, movieId is a foreign key, used to refer back to specific records in the Movie table.

With Data Connect, the Movie table looks like this:

type
  id: UUID! @default(expr: "uuidV4()")
  title: String!
  genre: String
}

You can leave the id field out, because Data Connect will automatically generate a primary key for you. The generated fields will be in implicit.gql. The code above can be simplified to:

type
  title: String!
  genre: String
}

If you open implicit.gql, there will be an id field in the extended Movie class:

extend 
  id: UUID! @default(expr: "uuidV4()") @fdc_generated(from: "Movie",    
  purpose: IMPLICIT_KEY_FIELD)
}

Next, let’s see what the MovieMetadata table without the foreign key from Movie looks like:

type
  director: String
  rating: Float
  releaseYear: Int
}

Again, an id primary key gets automatically created and put into implicit.gql:

extend 
  id: UUID! @default(expr: "uuidV4()") @fdc_generated(from: "MovieMetadata", purpose: IMPLICIT_KEY_FIELD)
}

To add the foreign key with a one-to-one relationship, use @unique, which creates a field in the current table (MovieMetadata) and is a reference that holds the primary key of the referenced type:

type
  movie: Movie! @unique
  # movieId: UUID <- 
  director
  rating
  releaseYear
}

As a result, the movieId from the Movie table will be included in this table, and the movieId will match the one in the Movie table.

One-to-many relationships

In one-to-many relationships, one record in a table is associated with many records in another table. For example, let’s say there are users for your app, and each user can have one favorite movie while one movie can have many users for which it’s their favorite.

With Data Connect, the User table looks like this:

type
  id: String!
  username: String!
}

The FavoriteMovie table has this information:

type
  # User to FavoriteMovie is 1:1, as one user can have only one favorite movie
  user: User! @unique
  # userId: UUID <- 
  
  # 
  movie
  # 
}

A combination of keys becomes the record’s unique identifier, the composite key. In this case for each record, the combination of user and movie needs to be unique, hence key: ["user", "movie"].

You can reuse @unique to signal that all users should be unique in this FavoriteMovie table, since each user has one favorite movie. The movie field, however, does not need to be unique because one movie can be many users’ favorite movie (a many-to-many relationship, which gets covered in the next section).

@ref for Movie is similar to @unique, in that it creates a field in the current table (FavoriteMovie) to reference the primary key of the referenced type. In this case, @ref(fields: "movieId", references: "id") is implied:

  • fields: “movieId” is the name of the foreign key received from the Movie table
  • references: “id” means the movieId field is going to be the id from the Movie table

Writing @ref for Movie is optional, as it’s implied, so the former FavoriteMovie table’s code can be simplified to:

type
  user: User! @unique
  # userId: UUID <- 
  movie
  # 
}

Whenever an implicit reference is made, implicit.gql includes the type:

extend 
  userId: String! @fdc_generated(from: "FavoriteMovie.user", purpose: IMPLICIT_REF_FIELD)
  movieId: UUID! @fdc_generated(from: "FavoriteMovie.movie", purpose: IMPLICIT_REF_FIELD)
}

There is no id field since the combination of userId and movieId is the composite key for each record.

Many-to-many relationships

In many-to-many relationships, many records in a table are associated with many records in another table, and typically, a join table is used to store these relations. An example of this is an actor being in many movies, and a movie having many actors. The table can hold additional information like whether the actor is a main or supporting actor for a movie.

With Data Connect, the Actor table looks like this:

type
  name: String!
}

We then create a join table between Actor and MovieActors to hold the many-to-many relationships information:

type
  movie: Movie!
  # movieId: UUID! <- 
  actor
  # 
  role
}

Again, we declare a composite key with key: ["movie", "actor"], and there can be the same movie repeated multiple times in the table, same with actors. For each movie-actor pair, the table will say whether the actor is a main or supporting actor for that movie.

With the implied references, implicit.gql will extend the MovieActor class to include:

extend 
  movieId: UUID! @fdc_generated(from: "MovieActor.movie", purpose: IMPLICIT_REF_FIELD)
  actorId: UUID! @fdc_generated(from: "MovieActor.actor", purpose: IMPLICIT_REF_FIELD)
}

Production Web Architecture & Security Checklist

Whether we are deploying on Firebase App Hosting, Cloudflare Workers, or Vercel, shipping SQL features to production requires defense-in-depth across three layers:

  • Enforce Firebase App Check & Attestation: Protect your web endpoints, Cloud Functions, Data Connect GraphQL resolvers, and Firebase AI Logic calls against unauthorized replay or billing abuse using Firebase App Check with reCAPTCHA Enterprise.
  • Validate Locally with the Firebase Emulator Suite: Run deterministic integration tests against local Firestore, Auth, Functions, and Data Connect PostgreSQL emulators before promoting schema or rule changes to production.
  • Keep Server & Client Boundaries Type-Safe: Use the modular Firebase Web SDK and generated TypeScript bindings so SSR frameworks like Next.js, Astro, SvelteKit, and Angular serialize state cleanly without hydration flickers.

Conclusion

Accelerate your full-stack web and AI development by pairing How Relationships Work in Data Connect with strict Security Rules, Firebase App Check, and local emulator testing. Come tell us on X (@CodingCatDev) and LinkedIn (CodingCat.dev) how you are using Firebase in your stack, and subscribe to the CodingCat.dev YouTube Channel so you don't miss what we build next!

Watch Our Related Firebase Video Tutorial on YouTube

Want to see how we architect and debug Firebase web applications live? Watch our related CodingCat.dev session Firebase SQL with Data Connect below, and make sure to Subscribe to CodingCat.dev on YouTube for weekly full-stack web and AI deep dives:

Resources, Official Docs & Related CodingCat.dev Guides

Frequently Asked Questions (FAQ)

What are the key capabilities introduced in How Relationships Work in Data Connect?

How Relationships Work in Data Connect (announced 2025-01-28 by the Firebase team) enhances the platform for Firebase, Google Cloud, Web Dev, SQL workloads. Note: Firebase Data Connect has been renamed to SQL Connect. Learn more in the SQL Connect announcement. Developers can review the primary announcement on the Firebase Blog alongside our implementation patterns and production checklist above.

How do web developers use How Relationships Work in Data Connect with Next.js, SvelteKit, React, or Angular?

Web developers can integrate these features using the modular Firebase JavaScript/TypeScript SDK, Firebase Data Connect web bindings, and Firebase App Hosting. Always pair client-side SDK initialization with Firebase App Check and strict Security Rules, and test locally with the Firebase Local Emulator Suite before deploying.

Where can I watch video tutorials on Firebase Web and AI development?

You can watch our hands-on Firebase video walkthroughs on the CodingCat.dev YouTube Channel, including Firebase SQL with Data Connect, and follow us on X (@CodingCatDev) for weekly full-stack tutorials.