---
title: "How Relationships Work in Data Connect"
description: "How Relationships Work in Data Connect"
published: "2025-01-28"
last_updated: "2025-01-28"
authors:
  - "Andrea Wu (Developer Advocate)"
categories:
  - "SQL Connect"
  - "GraphQL"
  - "PostgreSQL"
canonical_url: "https://firebase.blog/posts/2025/01/dataconnect-relationships"
---

# How Relationships Work in Data Connect

*Published January 28, 2025 by Andrea Wu (Developer Advocate)*

---
> **Note:** Firebase Data Connect has been renamed to SQL Connect. Learn more in the [SQL Connect announcement](https://firebase.blog/posts/2026/04/whats-new-sql-connect).

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](https://firebase.blog/posts/2024/10/dataconnect-graphql), and you can read the Data Connect [documentation](https://firebase.google.com/docs/data-connect/schemas-queries-mutations) 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. 

![One to one relationship table](https://firebasestorage.googleapis.com/v0/b/first-class-blog.appspot.com/o/posts%2Ffdc-relationships-onetoone_1600x800.webp?alt=media&token=e51ad6e9-70ea-43ca-b4bb-14675be50721)

With Data Connect, the `Movie` table looks like this: 

```javascript
type Movie @table {
  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:

```javascript
type Movie @table {
  title: String!
  genre: String
}
```

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

```javascript
extend type Movie {
  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:

```javascript
type MovieMetadata @table {
  director: String
  rating: Float
  releaseYear: Int
}
```

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

```javascript
extend type MovieMetadata {
  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:

```javascript
type MovieMetadata @table {
  movie: Movie! @unique
  # movieId: UUID <- this is created by the above @unique
  director: String
  rating: Float
  releaseYear: Int
}
```

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 our app, and each user can have one favorite movie while one movie can have many users for which it’s their favorite.

![One to many relationship table](https://firebasestorage.googleapis.com/v0/b/first-class-blog.appspot.com/o/posts%2Ffdc-relationships-onetomany_1600x800.webp?alt=media&token=3d65f018-6647-4269-9e64-9e3d5585c1be)

With Data Connect, the `User` table looks like this: 

```javascript
type User @table {
  id: String!
  username: String!
}
```

The `FavoriteMovie` table has this information:

```javascript
type FavoriteMovie @table(key: ["user", "movie"]) {
  # User to FavoriteMovie is 1:1, as one user can have only one favorite movie
  user: User! @unique
  # userId: UUID <- this is created by the above @unique
  
  # Movie to FavoriteMovie is 1:many, as one movie can be the favorite of many users. 
  movie: Movie! @ref
  # movieId: UUID <- this is created by the above @ref
}
```

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"]`.

We 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:

```javascript
type FavoriteMovie @table(key: ["user", "movie"]) {
  user: User! @unique
  # userId: UUID <- this is created by the above @unique
  movie: Movie!  
  # movieId: UUID <- this is created by the implied @ref
}
```

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

```javascript
extend type FavoriteMovie {
  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.

![Many to many relationships table](https://firebasestorage.googleapis.com/v0/b/first-class-blog.appspot.com/o/posts%2Ffdc-relationships-manytomany_1600x800.webp?alt=media&token=31a4335c-c695-43eb-80d0-4c5c9f3d79b6)

With Data Connect, the `Actor` table looks like this: 

```javascript
type Actor @table {
  name: String!
}
```

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

```javascript
type MovieActor @table(key: ["movie", "actor"]) {
  movie: Movie!
  # movieId: UUID! <- this is created by the implied @ref
  actor: Actor!
  # actorId: UUID! <- this is created by the implied @ref
  role: String! # "main" or "supporting"
}
```

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:

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

### Try it out!
Relationships are confusing and difficult (and not just the real-life kind), and hopefully this blog post clears up some of the muddy waters around how to create relationships in Data Connect. If you need more information about Data Connect, check out the [documentation](https://firebase.google.com/docs/data-connect), and you can also check out other deep-dive [articles](https://firebase.blog/posts/2024/10/dataconnect-series-announce). As it’s a new product, we’d love to hear your opinion and any [feature requests](https://firebase.uservoice.com/forums/948424-general?category_id=501596) 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!
