Skip to main content

Entity relations and inverse lookups

The term "entity relation" refers to the situation when an entity instance contains an instance of another entity within one of its fields. Type-wise this means that some entity (called the owning entity) has a field of a type that is some other, non-owning entity. Within the database, this is implemented as an (automatically indexed) foreign key column within the table mapped to the owning entity. A fieldName entity-typed field will map to a column named field_name_id.

One-to-one and one-to-many relations are supported by Typeorm. The "many" side of the one-to-many relations is always the owning side. Many-to-many relations are modeled as two one-to-many relations with an explicit join table.

An entity relation is always unidirectional, but it is possible to request the data on the owning entity from the non-owning one. To do so, define a field decorated @derivedFrom in the schema. Doing so will cause the Typeorm code generated by squid-typeorm-codegen and the GraphQL API served by squid-graphql-server to show a virtual (that is, not mapping to a database column) field populated via inverse lookup queries.

The following examples illustrate the concepts.

One-to-one relations

type Account @entity {
id: ID!
balance: BigInt!
user: User @derivedFrom(field: "account")
}

type User @entity {
id: ID!
account: Account! @unique
username: String!
creation: DateTime!
}

The User entity references Account and owns the one-to-one relation. This is implemented as follows:

  • On the database side: the account property of the User entity maps to the account_id foreign key column of the user table referencing the account table.
  • On the TypeORM side: the account property of the User entity gets decorated with @OneToOne and @JoinColumn.
  • On the GraphQL side: sub-selection of the account property is made available in user-related queries. Sub-selection of the user property is made available in account-related queries.
info

Unlike for the many-to-one case, the codegen will not add a virtual reverse lookup property to the TypeORM code for one-to-one relations. You can add it manually:

src/model/generated/account.model.ts
import {OneToOne as OneToOne_} from "typeorm"

@Entity_()
export class Account {
// ...
@OneToOne_(() => User, e => e.account)
user: User
}

If you are using this feature, please let us know at the SquidDevs Telegram channel.

Many-to-one/One-to-many relations

type Account @entity {
"Account address"
id: ID!
transfersTo: [Transfer!] @derivedFrom(field: "to")
transfersFrom: [Transfer!] @derivedFrom(field: "from")
}

type Transfer @entity {
id: ID!
to: Account!
from: Account!
amount: BigInt!
}

Here Tranfer defines owns the two relations and Account defines the corresponding inverse lookup properties. This is implemented as follows:

  • On the database side: the from and to properties of the Transfer entity map to from_id and to_id foreign key columns of the transfer table referencing the account table.
  • On the TypeORM side: properties to and from of the Transfer entity class get decorated with @ManyToOne. Properties transfersTo and transfersFrom decorated with @OneToMany get added to the Account entity class.
  • On the GraphQL side: sub-selection of all relation-defined properties is made available in the schema.

Many-to-many relations

Many-to-many entity relations should be modeled as two one-to-many relations with an explicitly defined join table. Here is an example:

# an explicit join table 
type TradeToken @entity {
id: ID! # This is required, even if useless
trade: Trade!
token: Token!
}

type Token @entity {
id: ID!
symbol: String!
trades: [TradeToken!]! @derivedFrom(field: "token")
}

type Trade @entity {
id: ID!
tokens: [TradeToken!]! @derivedFrom(field: "trade")
}